diff --git a/.mise.toml b/.mise.toml index befe0ad..d41748e 100644 --- a/.mise.toml +++ b/.mise.toml @@ -1,4 +1,4 @@ [tools] -python="3.11" +python="3.12" poetry="2.3.3" java="liberica-1.8.0" diff --git a/poetry.lock b/poetry.lock index 634f9e1..ec61a6d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,17 @@ # This file is automatically @generated by Poetry 2.3.3 and should not be changed by hand. +[[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 = "argcomplete" version = "3.6.3" @@ -17,23 +29,18 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] [[package]] name = "astroid" -version = "2.14.2" +version = "3.3.9" description = "An abstract syntax tree for Python with inference support." optional = false -python-versions = ">=3.7.2" +python-versions = ">=3.9.0" groups = ["dev", "lint"] files = [ - {file = "astroid-2.14.2-py3-none-any.whl", hash = "sha256:0e0e3709d64fbffd3037e4ff403580550f14471fd3eaae9fa11cc9a5c7901153"}, - {file = "astroid-2.14.2.tar.gz", hash = "sha256:a3cf9f02c53dd259144a7e8f3ccd75d67c9a8c716ef183e0c1f291bc5d7bb3cf"}, + {file = "astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248"}, + {file = "astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550"}, ] [package.dependencies] -lazy-object-proxy = ">=1.4.0" typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} -wrapt = [ - {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, - {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, -] [[package]] name = "behave" @@ -1072,19 +1079,19 @@ dev = ["black", "build", "mypy", "pytest", "pyupgrade", "twine", "validate-pypro [[package]] name = "delta-spark" -version = "2.4.0" +version = "3.2.0" description = "Python APIs for using Delta Lake with Apache Spark" optional = false python-versions = ">=3.6" groups = ["main"] files = [ - {file = "delta-spark-2.4.0.tar.gz", hash = "sha256:ef776e325e80d98e3920cab982c747b094acc46599d62dfcdc9035fb112ba6a9"}, - {file = "delta_spark-2.4.0-py3-none-any.whl", hash = "sha256:7204142a97ef16367403b020d810d0c37f4ae8275b4997de4056423cf69b3a4b"}, + {file = "delta-spark-3.2.0.tar.gz", hash = "sha256:641967828e47c64805f8c746513da80bea24b5f19b069cdcf64561cd3692e11d"}, + {file = "delta_spark-3.2.0-py3-none-any.whl", hash = "sha256:c4ff3fa7218e58a702cb71eb64384b0005c4d6f0bbdd0fe0b38a53564d946e09"}, ] [package.dependencies] importlib-metadata = ">=1.0.0" -pyspark = ">=3.4.0,<3.5.0" +pyspark = ">=3.5.0,<3.6.0" [[package]] name = "deprecated" @@ -1225,21 +1232,6 @@ typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "faker" -version = "18.11.1" -description = "Faker is a Python package that generates fake data for you." -optional = false -python-versions = ">=3.7" -groups = ["dev", "test"] -files = [ - {file = "Faker-18.11.1-py3-none-any.whl", hash = "sha256:02980fe15acd58861305568bae0a277680792a505a5a45a309d352f79c452dd1"}, - {file = "Faker-18.11.1.tar.gz", hash = "sha256:df4ee36d058a6a96de9d5e645571ef8536946a0b62db841494f8a3bc3bcdc5af"}, -] - -[package.dependencies] -python-dateutil = ">=2.4" - [[package]] name = "filelock" version = "3.29.0" @@ -1353,21 +1345,18 @@ files = [ [[package]] name = "isort" -version = "5.11.5" +version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" groups = ["dev", "lint"] files = [ - {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, - {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] [package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] +colors = ["colorama (>=0.4.6)"] [[package]] name = "jinja2" @@ -1400,57 +1389,103 @@ files = [ ] [[package]] -name = "lazy-object-proxy" -version = "1.12.0" -description = "A fast and thorough lazy object proxy." +name = "librt" +version = "0.11.0" +description = "Mypyc runtime library" optional = false python-versions = ">=3.9" groups = ["dev", "lint"] files = [ - {file = "lazy_object_proxy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61d5e3310a4aa5792c2b599a7a78ccf8687292c8eb09cf187cca8f09cf6a7519"}, - {file = "lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ca33565f698ac1aece152a10f432415d1a2aa9a42dfe23e5ba2bc255ab91f6"}, - {file = "lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01c7819a410f7c255b20799b65d36b414379a30c6f1684c7bd7eb6777338c1b"}, - {file = "lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:029d2b355076710505c9545aef5ab3f750d89779310e26ddf2b7b23f6ea03cd8"}, - {file = "lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc6e3614eca88b1c8a625fc0a47d0d745e7c3255b21dac0e30b3037c5e3deeb8"}, - {file = "lazy_object_proxy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:be5fe974e39ceb0d6c9db0663c0464669cf866b2851c73971409b9566e880eab"}, - {file = "lazy_object_proxy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf69cd1a6c7fe2dbcc3edaa017cf010f4192e53796538cc7d5e1fedbfa4bcff"}, - {file = "lazy_object_proxy-1.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efff4375a8c52f55a145dc8487a2108c2140f0bec4151ab4e1843e52eb9987ad"}, - {file = "lazy_object_proxy-1.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1192e8c2f1031a6ff453ee40213afa01ba765b3dc861302cd91dbdb2e2660b00"}, - {file = "lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3605b632e82a1cbc32a1e5034278a64db555b3496e0795723ee697006b980508"}, - {file = "lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a61095f5d9d1a743e1e20ec6d6db6c2ca511961777257ebd9b288951b23b44fa"}, - {file = "lazy_object_proxy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:997b1d6e10ecc6fb6fe0f2c959791ae59599f41da61d652f6c903d1ee58b7370"}, - {file = "lazy_object_proxy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0d6027b760a11cc18281e702c0309dd92da458a74b4c15025d7fc490deede"}, - {file = "lazy_object_proxy-1.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ab2c584e3cc8be0dfca422e05ad30a9abe3555ce63e9ab7a559f62f8dbc6ff9"}, - {file = "lazy_object_proxy-1.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14e348185adbd03ec17d051e169ec45686dcd840a3779c9d4c10aabe2ca6e1c0"}, - {file = "lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4fcbe74fb85df8ba7825fa05eddca764138da752904b378f0ae5ab33a36c308"}, - {file = "lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:563d2ec8e4d4b68ee7848c5ab4d6057a6d703cb7963b342968bb8758dda33a23"}, - {file = "lazy_object_proxy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:53c7fd99eb156bbb82cbc5d5188891d8fdd805ba6c1e3b92b90092da2a837073"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f"}, - {file = "lazy_object_proxy-1.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c9defba70ab943f1df98a656247966d7729da2fe9c2d5d85346464bf320820a3"}, - {file = "lazy_object_proxy-1.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6763941dbf97eea6b90f5b06eb4da9418cc088fce0e3883f5816090f9afcde4a"}, - {file = "lazy_object_proxy-1.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdc70d81235fc586b9e3d1aeef7d1553259b62ecaae9db2167a5d2550dcc391a"}, - {file = "lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0a83c6f7a6b2bfc11ef3ed67f8cbe99f8ff500b05655d8e7df9aab993a6abc95"}, - {file = "lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:256262384ebd2a77b023ad02fbcc9326282bcfd16484d5531154b02bc304f4c5"}, - {file = "lazy_object_proxy-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7601ec171c7e8584f8ff3f4e440aa2eebf93e854f04639263875b8c2971f819f"}, - {file = "lazy_object_proxy-1.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae575ad9b674d0029fc077c5231b3bc6b433a3d1a62a8c363df96974b5534728"}, - {file = "lazy_object_proxy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31020c84005d3daa4cc0fa5a310af2066efe6b0d82aeebf9ab199292652ff036"}, - {file = "lazy_object_proxy-1.12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800f32b00a47c27446a2b767df7538e6c66a3488632c402b4fb2224f9794f3c0"}, - {file = "lazy_object_proxy-1.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:15400b18893f345857b9e18b9bd87bd06aba84af6ed086187add70aeaa3f93f1"}, - {file = "lazy_object_proxy-1.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3d3964fbd326578bcdfffd017ef101b6fb0484f34e731fe060ba9b8816498c36"}, - {file = "lazy_object_proxy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:424a8ab6695400845c39f13c685050eab69fa0bbac5790b201cd27375e5e41d7"}, - {file = "lazy_object_proxy-1.12.0-pp39.pp310.pp311.graalpy311-none-any.whl", hash = "sha256:c3b2e0af1f7f77c4263759c4824316ce458fabe0fceadcd24ef8ca08b2d1e402"}, - {file = "lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61"}, + {file = "librt-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f"}, + {file = "librt-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45"}, + {file = "librt-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c"}, + {file = "librt-0.11.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33"}, + {file = "librt-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884"}, + {file = "librt-0.11.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280"}, + {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c"}, + {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb"}, + {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783"}, + {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0"}, + {file = "librt-0.11.0-cp310-cp310-win32.whl", hash = "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89"}, + {file = "librt-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4"}, + {file = "librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29"}, + {file = "librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9"}, + {file = "librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5"}, + {file = "librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b"}, + {file = "librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89"}, + {file = "librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc"}, + {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5"}, + {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7"}, + {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d"}, + {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412"}, + {file = "librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d"}, + {file = "librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73"}, + {file = "librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c"}, + {file = "librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46"}, + {file = "librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3"}, + {file = "librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67"}, + {file = "librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a"}, + {file = "librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a"}, + {file = "librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f"}, + {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b"}, + {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766"}, + {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d"}, + {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8"}, + {file = "librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a"}, + {file = "librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9"}, + {file = "librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c"}, + {file = "librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894"}, + {file = "librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c"}, + {file = "librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea"}, + {file = "librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230"}, + {file = "librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2"}, + {file = "librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3"}, + {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21"}, + {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930"}, + {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be"}, + {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e"}, + {file = "librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e"}, + {file = "librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47"}, + {file = "librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44"}, + {file = "librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd"}, + {file = "librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4"}, + {file = "librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8"}, + {file = "librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b"}, + {file = "librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175"}, + {file = "librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03"}, + {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c"}, + {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3"}, + {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96"}, + {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe"}, + {file = "librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f"}, + {file = "librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7"}, + {file = "librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1"}, + {file = "librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72"}, + {file = "librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa"}, + {file = "librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548"}, + {file = "librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2"}, + {file = "librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f"}, + {file = "librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51"}, + {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2"}, + {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085"}, + {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3"}, + {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd"}, + {file = "librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8"}, + {file = "librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c"}, + {file = "librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253"}, + {file = "librt-0.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6bd72d903911d995ab666dbd1871f8b1e80925a699af8063fbf50053329fb05f"}, + {file = "librt-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ef69ac715f3cd8e5cd252cb2aebfa72c015492aacc339d5d7bf8fef3c62c677"}, + {file = "librt-0.11.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:624a40c4a4ad7773315c287276cd024509b2c66ff5904f504bfc08d2c70293ab"}, + {file = "librt-0.11.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:41dc19fe150b69716c8ece4f76773a9e8813fe3e35e032a58b4d46423fb8d7c0"}, + {file = "librt-0.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e8bd98ea9c47ae90b319a087ab28dac493f1ffbc1ecd1f28fcdbf3b7e1108d1"}, + {file = "librt-0.11.0-cp39-cp39-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84308fc49423ce6475d1c5d1985cd69a8ca9f0325fc7d5f81bb690a3f3625d4e"}, + {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ff0fbaf5f44a21beeb0110f2ab64f45135a9536a834b79c0d1ef018f2786bbfa"}, + {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9c028a9442a18e266955d364ce42259136e79a7ba14d773e0d778d5f70cd56f1"}, + {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9f1692105a02bcf853f355032a5fdc5494358ef83d8fd22d16de375c85cec3f5"}, + {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7a80a71e1fda83cc752a9141e87aae7fef279538597564d670e9ce513f286192"}, + {file = "librt-0.11.0-cp39-cp39-win32.whl", hash = "sha256:140695816ddf3c86eb972981a26f35efd871c44b0c3aed44c8cd01749386617f"}, + {file = "librt-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:92f7ff819c197fc30473190a12c2856f325ac90aabfccbeb2072d28cc2e234e3"}, + {file = "librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1"}, ] [[package]] @@ -1812,14 +1847,14 @@ typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [[package]] name = "moto" -version = "4.0.13" -description = "A library that allows your python tests to easily mock out the boto library" +version = "4.2.14" +description = "" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["dev", "test"] files = [ - {file = "moto-4.0.13-py3-none-any.whl", hash = "sha256:e73400c6d3fe06028aa7f07bb6f276f14260d289b70f38928a98e3d3d968352d"}, - {file = "moto-4.0.13.tar.gz", hash = "sha256:baf7d6969cf837990c730e6e648315bebc2e1c0038d9d8fc4f59d03561484469"}, + {file = "moto-4.2.14-py2.py3-none-any.whl", hash = "sha256:6d242dbbabe925bb385ddb6958449e5c827670b13b8e153ed63f91dbdb50372c"}, + {file = "moto-4.2.14.tar.gz", hash = "sha256:8f9263ca70b646f091edcc93e97cda864a542e6d16ed04066b1370ed217bd190"}, ] [package.dependencies] @@ -1827,7 +1862,7 @@ boto3 = ">=1.9.201" botocore = ">=1.12.201" cryptography = ">=3.3.1" Jinja2 = ">=2.10.1" -MarkupSafe = "!=2.0.0a1" +py-partiql-parser = {version = "0.5.0", optional = true, markers = "extra == \"s3\""} python-dateutil = ">=2.1,<3.0.0" PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"s3\""} requests = ">=2.5" @@ -1836,78 +1871,94 @@ werkzeug = ">=0.5,<2.2.0 || >2.2.0,<2.2.1 || >2.2.1" xmltodict = "*" [package.extras] -all = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=2.5.1)", "ecdsa (!=0.15)", "graphql-core", "idna (>=2.5,<4)", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.2.8)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] -apigateway = ["PyYAML (>=5.1)", "ecdsa (!=0.15)", "openapi-spec-validator (>=0.2.8)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"] +all = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] +apigateway = ["PyYAML (>=5.1)", "ecdsa (!=0.15)", "openapi-spec-validator (>=0.5.0)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"] apigatewayv2 = ["PyYAML (>=5.1)"] appsync = ["graphql-core"] -awslambda = ["docker (>=2.5.1)"] -batch = ["docker (>=2.5.1)"] -cloudformation = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=2.5.1)", "ecdsa (!=0.15)", "graphql-core", "idna (>=2.5,<4)", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.2.8)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] +awslambda = ["docker (>=3.0.0)"] +batch = ["docker (>=3.0.0)"] +cloudformation = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] cognitoidp = ["ecdsa (!=0.15)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"] -ds = ["sshpubkeys (>=3.1.0)"] -dynamodb = ["docker (>=2.5.1)"] -dynamodb2 = ["docker (>=2.5.1)"] -dynamodbstreams = ["docker (>=2.5.1)"] -ebs = ["sshpubkeys (>=3.1.0)"] +dynamodb = ["docker (>=3.0.0)", "py-partiql-parser (==0.5.0)"] +dynamodbstreams = ["docker (>=3.0.0)", "py-partiql-parser (==0.5.0)"] ec2 = ["sshpubkeys (>=3.1.0)"] -efs = ["sshpubkeys (>=3.1.0)"] glue = ["pyparsing (>=3.0.7)"] iotdata = ["jsondiff (>=1.1.2)"] -route53resolver = ["sshpubkeys (>=3.1.0)"] -s3 = ["PyYAML (>=5.1)"] -server = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=2.5.1)", "ecdsa (!=0.15)", "flask (!=2.2.0,!=2.2.1)", "flask-cors", "graphql-core", "idna (>=2.5,<4)", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.2.8)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] -ssm = ["PyYAML (>=5.1)", "dataclasses ; python_version < \"3.7\""] +proxy = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=2.5.1)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] +resourcegroupstaggingapi = ["PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"] +s3 = ["PyYAML (>=5.1)", "py-partiql-parser (==0.5.0)"] +s3crc32c = ["PyYAML (>=5.1)", "crc32c", "py-partiql-parser (==0.5.0)"] +server = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "flask (!=2.2.0,!=2.2.1)", "flask-cors", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] +ssm = ["PyYAML (>=5.1)"] xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] [[package]] name = "mypy" -version = "0.991" +version = "1.20.2" description = "Optional static typing for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" groups = ["dev", "lint"] files = [ - {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, - {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, - {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, - {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"}, - {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"}, - {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"}, - {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"}, - {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"}, - {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"}, - {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"}, - {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"}, - {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"}, - {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"}, - {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"}, - {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"}, - {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"}, - {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"}, - {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"}, - {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"}, - {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"}, - {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"}, - {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"}, - {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, - {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, + {file = "mypy-1.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4"}, + {file = "mypy-1.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997"}, + {file = "mypy-1.20.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14"}, + {file = "mypy-1.20.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99"}, + {file = "mypy-1.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c"}, + {file = "mypy-1.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd"}, + {file = "mypy-1.20.2-cp310-cp310-win_arm64.whl", hash = "sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2"}, + {file = "mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c"}, + {file = "mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3"}, + {file = "mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254"}, + {file = "mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98"}, + {file = "mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac"}, + {file = "mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67"}, + {file = "mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100"}, + {file = "mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b"}, + {file = "mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4"}, + {file = "mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6"}, + {file = "mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066"}, + {file = "mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102"}, + {file = "mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9"}, + {file = "mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58"}, + {file = "mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026"}, + {file = "mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943"}, + {file = "mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517"}, + {file = "mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15"}, + {file = "mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee"}, + {file = "mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f"}, + {file = "mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330"}, + {file = "mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30"}, + {file = "mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924"}, + {file = "mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb"}, + {file = "mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc"}, + {file = "mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558"}, + {file = "mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8"}, + {file = "mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3"}, + {file = "mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609"}, + {file = "mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2"}, + {file = "mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c"}, + {file = "mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744"}, + {file = "mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6"}, + {file = "mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec"}, + {file = "mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382"}, + {file = "mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563"}, + {file = "mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665"}, ] [package.dependencies] -mypy-extensions = ">=0.4.3" +librt = {version = ">=0.8.0", markers = "platform_python_implementation != \"PyPy\""} +mypy_extensions = ">=1.0.0" +pathspec = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=3.10" +typing_extensions = {version = ">=4.6.0", markers = "python_version < \"3.15\""} [package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] +mypyc = ["setuptools (>=50)"] +native-parser = ["ast-serialize (>=0.1.1,<1.0.0)"] reports = ["lxml"] [[package]] @@ -2159,6 +2210,7 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, {version = ">=1.23.2", markers = "python_version == \"3.11\""}, ] python-dateutil = ">=2.8.2" @@ -2354,6 +2406,21 @@ files = [ [package.dependencies] wcwidth = "*" +[[package]] +name = "py-partiql-parser" +version = "0.5.0" +description = "Pure Python PartiQL Parser" +optional = false +python-versions = "*" +groups = ["dev", "test"] +files = [ + {file = "py-partiql-parser-0.5.0.tar.gz", hash = "sha256:427a662e87d51a0a50150fc8b75c9ebb4a52d49129684856c40c88b8c8e027e4"}, + {file = "py_partiql_parser-0.5.0-py3-none-any.whl", hash = "sha256:dc454c27526adf62deca5177ea997bf41fac4fd109c5d4c8d81f984de738ba8f"}, +] + +[package.extras] +dev = ["black (==22.6.0)", "flake8", "mypy", "pytest"] + [[package]] name = "py4j" version = "0.10.9.7" @@ -2433,56 +2500,158 @@ files = [ [[package]] name = "pydantic" -version = "1.10.16" -description = "Data validation and settings management using python type hints" +version = "2.13.4" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-1.10.16-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1a539ac40551b01a85e899829aa43ca8036707474af8d74b48be288d4d2d2846"}, - {file = "pydantic-1.10.16-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a4fcc7b0b8038dbda2dda642cff024032dfae24a7960cc58e57a39eb1949b9b"}, - {file = "pydantic-1.10.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4660dd697de1ae2d4305a85161312611f64d5360663a9ba026cd6ad9e3fe14c3"}, - {file = "pydantic-1.10.16-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:900a787c574f903a97d0bf52a43ff3b6cf4fa0119674bcfc0e5fd1056d388ad9"}, - {file = "pydantic-1.10.16-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d30192a63e6d3334c3f0c0506dd6ae9f1dce7b2f8845518915291393a5707a22"}, - {file = "pydantic-1.10.16-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:16cf23ed599ca5ca937e37ba50ab114e6b5c387eb43a6cc533701605ad1be611"}, - {file = "pydantic-1.10.16-cp310-cp310-win_amd64.whl", hash = "sha256:8d23111f41d1e19334edd51438fd57933f3eee7d9d2fa8cc3f5eda515a272055"}, - {file = "pydantic-1.10.16-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef287b8d7fc0e86a8bd1f902c61aff6ba9479c50563242fe88ba39692e98e1e0"}, - {file = "pydantic-1.10.16-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b9ded699bfd3b3912d796ff388b0c607e6d35d41053d37aaf8fd6082c660de9a"}, - {file = "pydantic-1.10.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:daeb199814333e4426c5e86d7fb610f4e230289f28cab90eb4de27330bef93cf"}, - {file = "pydantic-1.10.16-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5973843f1fa99ec6c3ac8d1a8698ac9340b35e45cca6c3e5beb5c3bd1ef15de6"}, - {file = "pydantic-1.10.16-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6b8a7788a8528a558828fe4a48783cafdcf2612d13c491594a8161dc721629c"}, - {file = "pydantic-1.10.16-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8abaecf54dacc9d991dda93c3b880d41092a8924cde94eeb811d7d9ab55df7d8"}, - {file = "pydantic-1.10.16-cp311-cp311-win_amd64.whl", hash = "sha256:ddc7b682fbd23f051edc419dc6977e11dd2dbdd0cef9d05f0e15d1387862d230"}, - {file = "pydantic-1.10.16-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:067c2b5539f7839653ad8c3d1fc2f1343338da8677b7b2172abf3cd3fdc8f719"}, - {file = "pydantic-1.10.16-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d1fc943583c046ecad0ff5d6281ee571b64e11b5503d9595febdce54f38b290"}, - {file = "pydantic-1.10.16-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18548b30ccebe71d380b0886cc44ea5d80afbcc155e3518792f13677ad06097d"}, - {file = "pydantic-1.10.16-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4e92292f9580fc5ea517618580fac24e9f6dc5657196e977c194a8e50e14f5a9"}, - {file = "pydantic-1.10.16-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5da8bc4bb4f85b8c97cc7f11141fddbbd29eb25e843672e5807e19cc3d7c1b7f"}, - {file = "pydantic-1.10.16-cp37-cp37m-win_amd64.whl", hash = "sha256:a04ee1ea34172b87707a6ecfcdb120d7656892206b7c4dbdb771a73e90179fcb"}, - {file = "pydantic-1.10.16-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4fa86469fd46e732242c7acb83282d33f83591a7e06f840481327d5bf6d96112"}, - {file = "pydantic-1.10.16-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:89c2783dc261726fe7a5ce1121bce29a2f7eb9b1e704c68df2b117604e3b346f"}, - {file = "pydantic-1.10.16-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78e59fa919fa7a192f423d190d8660c35dd444efa9216662273f36826765424b"}, - {file = "pydantic-1.10.16-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7e82a80068c77f4b074032e031e642530b6d45cb8121fc7c99faa31fb6c6b72"}, - {file = "pydantic-1.10.16-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d82d5956cee27a30e26a5b88d00a6a2a15a4855e13c9baf50175976de0dc282c"}, - {file = "pydantic-1.10.16-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b7b99424cc0970ff08deccb549b5a6ec1040c0b449eab91723e64df2bd8fdca"}, - {file = "pydantic-1.10.16-cp38-cp38-win_amd64.whl", hash = "sha256:d97a35e1ba59442775201657171f601a2879e63517a55862a51f8d67cdfc0017"}, - {file = "pydantic-1.10.16-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9d91f6866fd3e303c632207813ef6bc4d86055e21c5e5a0a311983a9ac5f0192"}, - {file = "pydantic-1.10.16-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d8d3c71d14c8bd26d2350c081908dbf59d5a6a8f9596d9ef2b09cc1e61c8662b"}, - {file = "pydantic-1.10.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b73e6386b439b4881d79244e9fc1e32d1e31e8d784673f5d58a000550c94a6c0"}, - {file = "pydantic-1.10.16-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f039881fb2ef86f6de6eacce6e71701b47500355738367413ccc1550b2a69cf"}, - {file = "pydantic-1.10.16-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3895ddb26f22bdddee7e49741486aa7b389258c6f6771943e87fc00eabd79134"}, - {file = "pydantic-1.10.16-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55b945da2756b5cef93d792521ad0d457fdf2f69fd5a2d10a27513f5281717dd"}, - {file = "pydantic-1.10.16-cp39-cp39-win_amd64.whl", hash = "sha256:22dd265c77c3976a34be78409b128cb84629284dfd1b69d2fa1507a36f84dc8b"}, - {file = "pydantic-1.10.16-py3-none-any.whl", hash = "sha256:aa2774ba5412fd1c5cb890d08e8b0a3bb5765898913ba1f61a65a4810f03cf29"}, - {file = "pydantic-1.10.16.tar.gz", hash = "sha256:8bb388f6244809af69ee384900b10b677a69f1980fdc655ea419710cffcb5610"}, + {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"}, + {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.6.0" +pydantic-core = "2.46.4" +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-core" +version = "2.46.4" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"}, + {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39"}, + {file = "pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d"}, + {file = "pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d"}, + {file = "pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb"}, + {file = "pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596"}, + {file = "pydantic_core-2.46.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae"}, + {file = "pydantic_core-2.46.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f444c499b3eefd3a92e348059471ea0c3a6e303d9c1cec09fa748fd9f895201"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3447661d99f75a3683a4cf5c87da72f2161964611864dbbeac7fbb118bb4bfc0"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b9bab013d1c7a79d3501ff86d0bc9c31bf587db4551677b96bec07df78c6b15"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d995260fdf4e1db774581b4900e0f832abe3c7c84996726bbc161b19c8f29e76"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13a646d65d09fbf1bc6b3a9635d30095c8e7e5cc419ff35ecc563c5fd04cd49"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432c179df7874eeb73307aad2df0755e1ae0efa61ff0ea89b93e194411ae3928"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:e68b7a074f65a2fd746c52a7ce6142ab7006074ac269ace0c25cd8ba171f8066"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a05d69cba51d852c5c3e92758653245a50c0b646ced0cf05bd793ed592839d6"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:228ee9bae8bef5b1e97ec58302f80357c37199e0d0a99174e138d28e6957b9d9"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:10e17cbb10a330363733efc4d7c4d0dd827ac0909b8f6a6542298fed1ea62f29"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:91a06d2e259ecfbd8c901d70c3c507900458498142b3026a296b7de4d1322cc9"}, + {file = "pydantic_core-2.46.4-cp39-cp39-win32.whl", hash = "sha256:d80ee3d731373b24cebbc10d689ca4ee1875caf0d5703a245db18efd4dd37fc1"}, + {file = "pydantic_core-2.46.4-cp39-cp39-win_amd64.whl", hash = "sha256:3be77f45df024d789a672ae34f8b06fb346c4f9f46ea714956660ea4862e89ac"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983"}, + {file = "pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" [[package]] name = "pygments" @@ -2501,27 +2670,28 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pylint" -version = "2.16.4" +version = "3.3.9" description = "python code static checker" optional = false -python-versions = ">=3.7.2" +python-versions = ">=3.9.0" groups = ["dev", "lint"] files = [ - {file = "pylint-2.16.4-py3-none-any.whl", hash = "sha256:4a770bb74fde0550fa0ab4248a2ad04e7887462f9f425baa0cd8d3c1d098eaee"}, - {file = "pylint-2.16.4.tar.gz", hash = "sha256:8841f26a0dbc3503631b6a20ee368b3f5e0e5461a1d95cf15d103dab748a0db3"}, + {file = "pylint-3.3.9-py3-none-any.whl", hash = "sha256:01f9b0462c7730f94786c283f3e52a1fbdf0494bbe0971a78d7277ef46a751e7"}, + {file = "pylint-3.3.9.tar.gz", hash = "sha256:d312737d7b25ccf6b01cc4ac629b5dcd14a0fcf3ec392735ac70f137a9d5f83a"}, ] [package.dependencies] -astroid = ">=2.14.2,<=2.16.0.dev0" +astroid = ">=3.3.8,<=3.4.0.dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, + {version = ">=0.3.6", markers = "python_version == \"3.11\""}, ] -isort = ">=4.2.5,<6" +isort = ">=4.2.5,<5.13 || >5.13,<7" mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +platformdirs = ">=2.2" +tomli = {version = ">=1.1", markers = "python_version < \"3.11\""} tomlkit = ">=0.10.1" [package.extras] @@ -2549,24 +2719,24 @@ extra = ["pygments (>=2.19.1)"] [[package]] name = "pyspark" -version = "3.4.4" +version = "3.5.2" description = "Apache Spark Python API" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pyspark-3.4.4.tar.gz", hash = "sha256:b831a99c1aeb058bc4a2498665de10006b899b2438ba15470cc88f577755ebf8"}, + {file = "pyspark-3.5.2.tar.gz", hash = "sha256:bbb36eba09fa24e86e0923d7e7a986041b90c714e11c6aa976f9791fe9edde5e"}, ] [package.dependencies] py4j = "0.10.9.7" [package.extras] -connect = ["googleapis-common-protos (>=1.56.4)", "grpcio (>=1.48.1)", "grpcio-status (>=1.48.1)", "numpy (>=1.15,<2)", "pandas (>=1.0.5)", "pyarrow (>=1.0.0)"] +connect = ["googleapis-common-protos (>=1.56.4)", "grpcio (>=1.56.0)", "grpcio-status (>=1.56.0)", "numpy (>=1.15,<2)", "pandas (>=1.0.5)", "pyarrow (>=4.0.0)"] ml = ["numpy (>=1.15,<2)"] mllib = ["numpy (>=1.15,<2)"] -pandas-on-spark = ["numpy (>=1.15,<2)", "pandas (>=1.0.5)", "pyarrow (>=1.0.0)"] -sql = ["numpy (>=1.15,<2)", "pandas (>=1.0.5)", "pyarrow (>=1.0.0)"] +pandas-on-spark = ["numpy (>=1.15,<2)", "pandas (>=1.0.5)", "pyarrow (>=4.0.0)"] +sql = ["numpy (>=1.15,<2)", "pandas (>=1.0.5)", "pyarrow (>=4.0.0)"] [[package]] name = "pytest" @@ -3049,6 +3219,21 @@ files = [ ] markers = {docs = "python_version == \"3.10\"", test = "python_version == \"3.10\""} +[[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 = "2026.2" @@ -3173,105 +3358,117 @@ watchdog = ["watchdog (>=2.3)"] [[package]] name = "wrapt" -version = "1.17.3" +version = "2.1.2" description = "Module for decorators, wrappers and monkey patching." optional = false -python-versions = ">=3.8" -groups = ["dev", "lint"] +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, - {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, - {file = "wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c"}, - {file = "wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775"}, - {file = "wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd"}, - {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05"}, - {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418"}, - {file = "wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390"}, - {file = "wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6"}, - {file = "wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18"}, - {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7"}, - {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85"}, - {file = "wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f"}, - {file = "wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311"}, - {file = "wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1"}, - {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5"}, - {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2"}, - {file = "wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89"}, - {file = "wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77"}, - {file = "wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a"}, - {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0"}, - {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba"}, - {file = "wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd"}, - {file = "wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828"}, - {file = "wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9"}, - {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396"}, - {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc"}, - {file = "wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe"}, - {file = "wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c"}, - {file = "wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6"}, - {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0"}, - {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77"}, - {file = "wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7"}, - {file = "wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277"}, - {file = "wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d"}, - {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa"}, - {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050"}, - {file = "wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8"}, - {file = "wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb"}, - {file = "wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16"}, - {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39"}, - {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235"}, - {file = "wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c"}, - {file = "wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b"}, - {file = "wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa"}, - {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7"}, - {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4"}, - {file = "wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10"}, - {file = "wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6"}, - {file = "wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58"}, - {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a"}, - {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067"}, - {file = "wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454"}, - {file = "wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e"}, - {file = "wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f"}, - {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056"}, - {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804"}, - {file = "wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977"}, - {file = "wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116"}, - {file = "wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6"}, - {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:70d86fa5197b8947a2fa70260b48e400bf2ccacdcab97bb7de47e3d1e6312225"}, - {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df7d30371a2accfe4013e90445f6388c570f103d61019b6b7c57e0265250072a"}, - {file = "wrapt-1.17.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:caea3e9c79d5f0d2c6d9ab96111601797ea5da8e6d0723f77eabb0d4068d2b2f"}, - {file = "wrapt-1.17.3-cp38-cp38-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:758895b01d546812d1f42204bd443b8c433c44d090248bf22689df673ccafe00"}, - {file = "wrapt-1.17.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02b551d101f31694fc785e58e0720ef7d9a10c4e62c1c9358ce6f63f23e30a56"}, - {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:656873859b3b50eeebe6db8b1455e99d90c26ab058db8e427046dbc35c3140a5"}, - {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a9a2203361a6e6404f80b99234fe7fb37d1fc73487b5a78dc1aa5b97201e0f22"}, - {file = "wrapt-1.17.3-cp38-cp38-win32.whl", hash = "sha256:55cbbc356c2842f39bcc553cf695932e8b30e30e797f961860afb308e6b1bb7c"}, - {file = "wrapt-1.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:ad85e269fe54d506b240d2d7b9f5f2057c2aa9a2ea5b32c66f8902f768117ed2"}, - {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc"}, - {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9"}, - {file = "wrapt-1.17.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d"}, - {file = "wrapt-1.17.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a"}, - {file = "wrapt-1.17.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139"}, - {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df"}, - {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b"}, - {file = "wrapt-1.17.3-cp39-cp39-win32.whl", hash = "sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81"}, - {file = "wrapt-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f"}, - {file = "wrapt-1.17.3-cp39-cp39-win_arm64.whl", hash = "sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f"}, - {file = "wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22"}, - {file = "wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0"}, + {file = "wrapt-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a86d99a14f76facb269dc148590c01aaf47584071809a70da30555228158c"}, + {file = "wrapt-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a819e39017f95bf7aede768f75915635aa8f671f2993c036991b8d3bfe8dbb6f"}, + {file = "wrapt-2.1.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5681123e60aed0e64c7d44f72bbf8b4ce45f79d81467e2c4c728629f5baf06eb"}, + {file = "wrapt-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8b28e97a44d21836259739ae76284e180b18abbb4dcfdff07a415cf1016c3e"}, + {file = "wrapt-2.1.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cef91c95a50596fcdc31397eb6955476f82ae8a3f5a8eabdc13611b60ee380ba"}, + {file = "wrapt-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dad63212b168de8569b1c512f4eac4b57f2c6934b30df32d6ee9534a79f1493f"}, + {file = "wrapt-2.1.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d307aa6888d5efab2c1cde09843d48c843990be13069003184b67d426d145394"}, + {file = "wrapt-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c87cf3f0c85e27b3ac7d9ad95da166bf8739ca215a8b171e8404a2d739897a45"}, + {file = "wrapt-2.1.2-cp310-cp310-win32.whl", hash = "sha256:d1c5fea4f9fe3762e2b905fdd67df51e4be7a73b7674957af2d2ade71a5c075d"}, + {file = "wrapt-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:d8f7740e1af13dff2684e4d56fe604a7e04d6c94e737a60568d8d4238b9a0c71"}, + {file = "wrapt-2.1.2-cp310-cp310-win_arm64.whl", hash = "sha256:1c6cc827c00dc839350155f316f1f8b4b0c370f52b6a19e782e2bda89600c7dc"}, + {file = "wrapt-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96159a0ee2b0277d44201c3b5be479a9979cf154e8c82fa5df49586a8e7679bb"}, + {file = "wrapt-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98ba61833a77b747901e9012072f038795de7fc77849f1faa965464f3f87ff2d"}, + {file = "wrapt-2.1.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:767c0dbbe76cae2a60dd2b235ac0c87c9cccf4898aef8062e57bead46b5f6894"}, + {file = "wrapt-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c691a6bc752c0cc4711cc0c00896fcd0f116abc253609ef64ef930032821842"}, + {file = "wrapt-2.1.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f3b7d73012ea75aee5844de58c88f44cf62d0d62711e39da5a82824a7c4626a8"}, + {file = "wrapt-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:577dff354e7acd9d411eaf4bfe76b724c89c89c8fc9b7e127ee28c5f7bcb25b6"}, + {file = "wrapt-2.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d7b6fd105f8b24e5bd23ccf41cb1d1099796524bcc6f7fbb8fe576c44befbc9"}, + {file = "wrapt-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:866abdbf4612e0b34764922ef8b1c5668867610a718d3053d59e24a5e5fcfc15"}, + {file = "wrapt-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5a0a0a3a882393095573344075189eb2d566e0fd205a2b6414e9997b1b800a8b"}, + {file = "wrapt-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:64a07a71d2730ba56f11d1a4b91f7817dc79bc134c11516b75d1921a7c6fcda1"}, + {file = "wrapt-2.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:b89f095fe98bc12107f82a9f7d570dc83a0870291aeb6b1d7a7d35575f55d98a"}, + {file = "wrapt-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2aad9c4cda28a8f0653fc2d487596458c2a3f475e56ba02909e950a9efa6a9"}, + {file = "wrapt-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6433ea84e1cfacf32021d2a4ee909554ade7fd392caa6f7c13f1f4bf7b8e8748"}, + {file = "wrapt-2.1.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c20b757c268d30d6215916a5fa8461048d023865d888e437fab451139cad6c8e"}, + {file = "wrapt-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79847b83eb38e70d93dc392c7c5b587efe65b3e7afcc167aa8abd5d60e8761c8"}, + {file = "wrapt-2.1.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f8fba1bae256186a83d1875b2b1f4e2d1242e8fac0f58ec0d7e41b26967b965c"}, + {file = "wrapt-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3d3b35eedcf5f7d022291ecd7533321c4775f7b9cd0050a31a68499ba45757c"}, + {file = "wrapt-2.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6f2c5390460de57fa9582bc8a1b7a6c86e1a41dfad74c5225fc07044c15cc8d1"}, + {file = "wrapt-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dfa9f2cf65d027b951d05c662cc99ee3bd01f6e4691ed39848a7a5fffc902b2"}, + {file = "wrapt-2.1.2-cp312-cp312-win32.whl", hash = "sha256:eba8155747eb2cae4a0b913d9ebd12a1db4d860fc4c829d7578c7b989bd3f2f0"}, + {file = "wrapt-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1c51c738d7d9faa0b3601708e7e2eda9bf779e1b601dce6c77411f2a1b324a63"}, + {file = "wrapt-2.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:c8e46ae8e4032792eb2f677dbd0d557170a8e5524d22acc55199f43efedd39bf"}, + {file = "wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b"}, + {file = "wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e"}, + {file = "wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb"}, + {file = "wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca"}, + {file = "wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267"}, + {file = "wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f"}, + {file = "wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8"}, + {file = "wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413"}, + {file = "wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6"}, + {file = "wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1"}, + {file = "wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf"}, + {file = "wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b"}, + {file = "wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18"}, + {file = "wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d"}, + {file = "wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015"}, + {file = "wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92"}, + {file = "wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf"}, + {file = "wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67"}, + {file = "wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a"}, + {file = "wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd"}, + {file = "wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f"}, + {file = "wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679"}, + {file = "wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9"}, + {file = "wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9"}, + {file = "wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e"}, + {file = "wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c"}, + {file = "wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a"}, + {file = "wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90"}, + {file = "wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586"}, + {file = "wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19"}, + {file = "wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508"}, + {file = "wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04"}, + {file = "wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575"}, + {file = "wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb"}, + {file = "wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22"}, + {file = "wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596"}, + {file = "wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044"}, + {file = "wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b"}, + {file = "wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf"}, + {file = "wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2"}, + {file = "wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3"}, + {file = "wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7"}, + {file = "wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5"}, + {file = "wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00"}, + {file = "wrapt-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5e0fa9cc32300daf9eb09a1f5bdc6deb9a79defd70d5356ba453bcd50aef3742"}, + {file = "wrapt-2.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:710f6e5dfaf6a5d5c397d2d6758a78fecd9649deb21f1b645f5b57a328d63050"}, + {file = "wrapt-2.1.2-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:305d8a1755116bfdad5dda9e771dcb2138990a1d66e9edd81658816edf51aed1"}, + {file = "wrapt-2.1.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0d8fc30a43b5fe191cf2b1a0c82bab2571dadd38e7c0062ee87d6df858dd06e"}, + {file = "wrapt-2.1.2-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a5d516e22aedb7c9c1d47cba1c63160b1a6f61ec2f3948d127cd38d5cfbb556f"}, + {file = "wrapt-2.1.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:45914e8efbe4b9d5102fcf0e8e2e3258b83a5d5fba9f8f7b6d15681e9d29ffe0"}, + {file = "wrapt-2.1.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:478282ebd3795a089154fb16d3db360e103aa13d3b2ad30f8f6aac0d2207de0e"}, + {file = "wrapt-2.1.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3756219045f73fb28c5d7662778e4156fbd06cf823c4d2d4b19f97305e52819c"}, + {file = "wrapt-2.1.2-cp39-cp39-win32.whl", hash = "sha256:b8aefb4dbb18d904b96827435a763fa42fc1f08ea096a391710407a60983ced8"}, + {file = "wrapt-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:e5aeab8fe15c3dff75cfee94260dcd9cded012d4ff06add036c28fae7718593b"}, + {file = "wrapt-2.1.2-cp39-cp39-win_arm64.whl", hash = "sha256:f069e113743a21a3defac6677f000068ebb931639f789b5b226598e247a4c89e"}, + {file = "wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8"}, + {file = "wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e"}, ] +[package.extras] +dev = ["pytest", "setuptools"] + [[package]] name = "xlsx2csv" -version = "0.8.2" +version = "0.8.4" description = "xlsx to csv converter" optional = false python-versions = "*" groups = ["dev", "test"] files = [ - {file = "xlsx2csv-0.8.2-py3-none-any.whl", hash = "sha256:adcc76589d664f2bd597564851cc97c4d6b8825bb19d09c621e36baefa2d4274"}, - {file = "xlsx2csv-0.8.2.tar.gz", hash = "sha256:cdd272c82f8b32f1cee76aeaef87b2ee3549661fddf90f7ecf2310967a16fc84"}, + {file = "xlsx2csv-0.8.4-py3-none-any.whl", hash = "sha256:52ab873fc7b2f2ca75d14aee8bd1985a9f5c1bcb3cc7b80df7a5d57a40a67473"}, + {file = "xlsx2csv-0.8.4.tar.gz", hash = "sha256:2aa809888826f6af5b26c77fc7f613f2bbeada0d8cc09e5a58e0f59684bb6911"}, ] [[package]] @@ -3343,5 +3540,5 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" -python-versions = ">=3.10,<3.12" -content-hash = "2655344a334cdaeec3e76c4c3752c89b2db1bbe1bc0532b1eaa5bb8018a914bc" +python-versions = ">=3.10,<3.13" +content-hash = "793e65e9dcefec411b9423ed3587c05121529b422f9299ac99cd157c7dec7c01" diff --git a/pylint_checkers/check_typing_imports.py b/pylint_checkers/check_typing_imports.py deleted file mode 100644 index 8e35529..0000000 --- a/pylint_checkers/check_typing_imports.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -A pylint checker to check that deprecated imports are not being -taken from `typing`. - -No longer in use since downgrade to Python 3.7. -""" - -from typing import TYPE_CHECKING - -from astroid.node_classes import Attribute, Import, ImportFrom, Name -from pylint.checkers import BaseChecker - -if TYPE_CHECKING: - from pylint.lint import PyLinter - - -DEPRECATED_REPLACEMENTS: dict[str, str] = { - "Tuple": "tuple", - "Callable": "collections.abc.Callable", - "Type": "type", - "Dict": "dict", - "List": "list", - "Set": "set", - "FrozenSet": "frozenset", - "DefaultDict": "collections.defaultdict", - "OrderedDict": "collections.OrderedDict", - "ChainMap": "collections.ChainMap", - "Counter": "collections.counter", - "Deque": "collections.deque", - "Pattern": "re.Pattern", - "Match": "re.Match", - "AbstractSet": "collections.abc.AbstractSet", - "ByteString": "collections.abc.ByteString", - "Collection": "collections.abc.Collection", - "Container": "collections.abc.Container", - "ItemsView": "collections.abc.ItemsView", - "KeysView": "collections.abc.KeysView", - "Mapping": "collections.abc.Mapping", - "MappingView": "collections.abc.MappingView", - "MutableMapping": "collections.abc.MutableMapping", - "MutableSequence": "collections.abc.MutableSequence", - "MutableSet": "collections.abc.MutableSet", - "Sequence": "collections.abc.Sequence", - "ValuesView": "collections.abc.ValuesView", - "Iterable": "collections.abc.Iterable", - "Iterator": "collections.abc.Iterator", - "Generator": "collections.abc.Generator", - "Hashable": "collections.abc.Hashable", - "Reversible": "collections.abc.Reversible", - "Sized": "collections.abc.Sized", - "Coroutine": "collections.abc.Coroutine", - "AsyncGenerator": "collections.abc.AsyncGenerator", - "AsyncIterable": "collections.abc.AsyncIterable", - "AsyncIterator": "collections.abc.AsyncIterator", - "Awaitable": "collections.abc.Awaitable", - "ContextManager": "contextlib.AbstractContextManager", - "AsyncContextManager": "contextlib.AbstractAsyncContextManager", -} -"""Deprecated typing imports and their replacements.""" - - -class TypingImportChecker(BaseChecker): - """A pylint 'checker' to validate that deprecated typing generics are not being used.""" - - name = "deprecated-typing-imports-checker" - msgs = { - "W1901": ( - "Using deprecated 'typing.%s' instead of %s'%s'", - "deprecated-typing-generic", - "Emitted when deprecated typing generics are used instead of their replacements.", - {"minversion": (3, 9)}, - ), - } - - def visit_import(self, node: Import): - """Check import usage.""" - for import_name, import_alias in node.names: - if import_name != "typing": - continue - - typing_name = import_alias or import_name - for attr_node in node.root().nodes_of_class(Attribute): - name_node = attr_node.expr - - if not isinstance(name_node, Name) or name_node.name != typing_name: - continue - - name = attr_node.attrname - replacement = DEPRECATED_REPLACEMENTS.get(name) - qualifier = "builtin " if "." not in replacement else "" - self.add_message( - "deprecated-typing-generic", - node=attr_node, - args=(name, qualifier, replacement), - ) - - def visit_importfrom(self, node: ImportFrom): - """Check import from usage.""" - if not node.modname == "typing": - return - - for name, _ in node.names: - replacement = DEPRECATED_REPLACEMENTS.get(name) - if replacement is not None: - qualifier = "builtin " if "." not in replacement else "" - self.add_message( - "deprecated-typing-generic", node=node, args=(name, qualifier, replacement) - ) - - -def register(linter: "PyLinter"): - """Register the checker.""" - linter.register_checker(TypingImportChecker(linter)) diff --git a/pyproject.toml b/pyproject.toml index fb633c3..64e4bc8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,21 +30,21 @@ packages = [ ] [tool.poetry.dependencies] -python = ">=3.10,<3.12" +python = ">=3.10,<3.13" # breaking changes beyond 3.12 boto3 = ">=1.34.162,<1.36" # breaking change beyond 1.36 botocore = ">=1.34.162,<1.36" # breaking change beyond 1.36 -delta-spark = "2.4.*" -duckdb = "1.1.*" # breaking changes beyond 1.1 -Jinja2 = "3.1.*" -lxml = "^4.9.1" +delta-spark = "3.2.0" +duckdb = "1.1.3" # breaking changes beyond 1.1 +Jinja2 = "3.1.6" +lxml = "4.9.4" numpy = "1.26.4" -openpyxl = "^3.1" -pandas = "^2.2.2" -polars = "0.20.*" -pyarrow = "^17.0.0" -pydantic = "1.10.16" -pyspark = "3.4.*" -typing_extensions = "^4.6.2" +openpyxl = "3.1.5" +pandas = "2.3.3" +polars = "0.20.31" +pyarrow = "17.0.0" +pydantic = "2.13.4" +pyspark = "3.5.2" +typing_extensions = "4.15.0" [tool.poetry.group.dev] optional = true @@ -58,30 +58,30 @@ commitizen = "4.9.1" pre-commit = "4.3.0" charset-normalizer = "3.4.6" python-discovery = "1.2.0" -requests = "2.33.0" [tool.poetry.group.test] optional = true [tool.poetry.group.test.dependencies] -faker = "18.11.1" behave = "1.3.3" coverage = "7.11.0" -moto = {extras = ["s3"], version = "4.0.13"} +moto = {extras = ["s3"], version = "4.2.14"} +requests = "2.33.0" # dependency of `moto` Werkzeug = "3.1.6" pytest = "8.4.2" pytest-lazy-fixtures = "1.4.0" # switched from https://github.com/TvoroG/pytest-lazy-fixture as it's no longer supported -xlsx2csv = "0.8.2" +xlsx2csv = "0.8.4" # polars requirement [tool.poetry.group.lint] optional = true [tool.poetry.group.lint.dependencies] black = "24.3.0" -astroid = "2.14.2" -isort = "5.11.5" -pylint = "2.16.4" -mypy = "0.991" +astroid = "3.3.9" +isort = "5.13.2" +pylint = "3.3.9" +mypy = "1.20.2" +librt = "0.11.0" # mypy dependency boto3-stubs = {extras = ["essential"], version = "1.26.72"} botocore-stubs = "1.29.72" pandas-stubs = "1.2.0.62" @@ -100,7 +100,7 @@ optional = true [tool.poetry.group.docs.dependencies] click = "8.2.1" -mkdocs = "^1.6.1" +mkdocs = "1.6.1" mkdocstrings = { version = "1.0.3", extras = ["python"] } griffelib = "2.0.1" pymdown-extensions = "10.21.2" @@ -142,10 +142,6 @@ source_pkgs = [ [tool.coverage.report] show_missing = true -[tool.pylint] -init-hook = "import sys; sys.path.append('./pylint_checkers')" -load-plugins = "check_typing_imports" - [tool.pylint.main] extension-pkg-allow-list = ["pyspark", "lxml", "pydantic"] fail-under = 10.0 @@ -194,7 +190,7 @@ max-statements = 50 min-public-methods = 2 [tool.pylint.exceptions] -overgeneral-exceptions = ["BaseException", "Exception"] +overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"] [tool.pylint.format] ignore-long-lines = "^\\s*(# )??$" @@ -229,6 +225,8 @@ disable = [ "use-symbolic-message-instead", "logging-fstring-interpolation", "fixme", + "too-many-positional-arguments", + "too-many-arguments", ] enable = ["c-extension-no-member"] diff --git a/src/dve/core_engine/backends/base/auditing.py b/src/dve/core_engine/backends/base/auditing.py index d120fcb..c921cd9 100644 --- a/src/dve/core_engine/backends/base/auditing.py +++ b/src/dve/core_engine/backends/base/auditing.py @@ -14,7 +14,7 @@ from types import TracebackType from typing import Any, ClassVar, Generic, Optional, TypeVar, Union -from pydantic import ValidationError, validate_arguments +from pydantic import ValidationError, validate_call from typing_extensions import Literal, get_origin from dve.core_engine.models import ( @@ -98,8 +98,8 @@ def __init__(self, name: str, record_type: type[AuditRecord]): def schema(self) -> dict[str, type]: """Determine python schema of auditor""" return { - fld: str if get_origin(mdl.type_) == Literal else mdl.type_ - for fld, mdl in self._record_type.__fields__.items() + fld: str if get_origin(mdl.annotation) == Literal else mdl.annotation # type: ignore + for fld, mdl in self._record_type.model_fields.items() } @staticmethod @@ -195,7 +195,7 @@ def conv_to_iterable(recs: Union[AuditorType, AuditReturnType]) -> Iterable[dict """Convert AuditReturnType to iterable of dictionaries""" raise NotImplementedError() - @validate_arguments + @validate_call def add_processing_records(self, processing_records: list[ProcessingStatusRecord]): """Add an entry to the processing_status auditor.""" if self.pool: @@ -207,7 +207,7 @@ def add_processing_records(self, processing_records: list[ProcessingStatusRecord records=[dict(rec) for rec in processing_records] ) - @validate_arguments + @validate_call def add_submission_statistics_records(self, sub_stats: list[SubmissionStatisticsRecord]): """Add an entry to the submission statistics auditor.""" if self.pool: @@ -217,7 +217,7 @@ def add_submission_statistics_records(self, sub_stats: list[SubmissionStatistics ) return self._submission_statistics.add_records(records=[dict(rec) for rec in sub_stats]) - @validate_arguments + @validate_call def add_transfer_records(self, transfer_records: list[TransferRecord]): """Add an entry to the transfers auditor""" if self.pool: @@ -226,7 +226,7 @@ def add_transfer_records(self, transfer_records: list[TransferRecord]): ) return self._transfers.add_records(records=[dict(rec) for rec in transfer_records]) - @validate_arguments + @validate_call def add_new_submissions( self, submissions: list[SubmissionMetadata], @@ -249,7 +249,7 @@ def add_new_submissions( processing_status="received", job_run_id=job_run_id, **ts_info, - ).dict(), + ).model_dump(), } processing_status_recs.append(processing_rec) if sub_info: diff --git a/src/dve/core_engine/backends/implementations/duckdb/auditing.py b/src/dve/core_engine/backends/implementations/duckdb/auditing.py index 3124c6d..b3c8b75 100644 --- a/src/dve/core_engine/backends/implementations/duckdb/auditing.py +++ b/src/dve/core_engine/backends/implementations/duckdb/auditing.py @@ -14,10 +14,10 @@ OrderCriteria, ) from dve.core_engine.backends.implementations.duckdb.duckdb_helpers import ( - PYTHON_TYPE_TO_DUCKDB_TYPE, + get_duckdb_type_from_annotation, table_exists, ) -from dve.core_engine.backends.utilities import PYTHON_TYPE_TO_POLARS_TYPE +from dve.core_engine.backends.utilities import get_polars_type_from_annotation from dve.core_engine.models import ( AuditRecord, ProcessingStatusRecord, @@ -62,7 +62,10 @@ def ddb_create_table_sql(self) -> str: """Generate create table sql script for auditor""" _sql_expression = f"CREATE TABLE {self._name} (" _sql_expression += ", ".join( - [f"{fld} {PYTHON_TYPE_TO_DUCKDB_TYPE.get(dtype)}" for fld, dtype in self.schema.items()] + [ + f"{fld} {get_duckdb_type_from_annotation(dtype)}" + for fld, dtype in self.schema.items() + ] ) _sql_expression += ")" return _sql_expression @@ -70,10 +73,7 @@ def ddb_create_table_sql(self) -> str: @property def polars_schema(self) -> dict[str, PolarsType]: """Get polars dataframe schema for auditor""" - return { - fld: PYTHON_TYPE_TO_POLARS_TYPE.get(dtype, pl.Utf8) # type: ignore - for fld, dtype in self.schema.items() - } + return {fld: get_polars_type_from_annotation(dtype) for fld, dtype in self.schema.items()} def get_relation(self) -> DuckDBPyRelation: """Get a relation to interact with the auditor duckdb table""" @@ -106,7 +106,7 @@ def conv_to_entity(self, recs: list[AuditRecord]) -> DuckDBPyRelation: """Convert a list of audit records to a relation""" # pylint: disable=W0612 rec_df = pl.DataFrame( # type: ignore - [rec.dict() for rec in recs], + [rec.model_dump() for rec in recs], schema=self.polars_schema, ) return self._connection.sql("select * from rec_df") diff --git a/src/dve/core_engine/backends/implementations/duckdb/contract.py b/src/dve/core_engine/backends/implementations/duckdb/contract.py index 3595716..d9bb9bc 100644 --- a/src/dve/core_engine/backends/implementations/duckdb/contract.py +++ b/src/dve/core_engine/backends/implementations/duckdb/contract.py @@ -14,7 +14,6 @@ from duckdb.typing import DuckDBPyType from polars.datatypes.classes import DataTypeClass as PolarsType from pydantic import BaseModel -from pydantic.fields import ModelField import dve.parser.file_handling as fh from dve.common.error_utils import ( @@ -96,8 +95,8 @@ def create_entity_from_py_iterator( # pylint: disable=unused-argument ) -> DuckDBPyRelation: """Create DuckDB Relation from iterator of records""" polars_schema: dict[str, PolarsType] = { - fld.name: get_polars_type_from_annotation(fld.type_) - for fld in stringify_model(schema).__fields__.values() + name: get_polars_type_from_annotation(fld.annotation) + for name, fld in stringify_model(schema).model_fields.items() } _lazy_df = pl.LazyFrame(records, polars_schema) # type: ignore # pylint: disable=unused-variable return self._connection.sql("select * from _lazy_df") @@ -130,17 +129,15 @@ def apply_data_contract( ) as msg_writer: for entity_name, relation in entities.items(): # get dtypes for all fields -> python data types or use with relation - entity_fields: dict[str, ModelField] = contract_metadata.schemas[ - entity_name - ].__fields__ + entity_fields = contract_metadata.schemas[entity_name].model_fields ddb_schema: dict[str, DuckDBPyType] = { - fld.name: get_duckdb_type_from_annotation(fld.annotation) - for fld in entity_fields.values() + name: get_duckdb_type_from_annotation(fld.annotation) + for name, fld in entity_fields.items() } ddb_schema[RECORD_INDEX_COLUMN_NAME] = get_duckdb_type_from_annotation(int) polars_schema: dict[str, PolarsType] = { - fld.name: get_polars_type_from_annotation(fld.annotation) - for fld in entity_fields.values() + name: get_polars_type_from_annotation(fld.annotation) + for name, fld in entity_fields.items() } polars_schema[RECORD_INDEX_COLUMN_NAME] = get_polars_type_from_annotation(int) if relation_is_empty(relation): diff --git a/src/dve/core_engine/backends/implementations/duckdb/duckdb_helpers.py b/src/dve/core_engine/backends/implementations/duckdb/duckdb_helpers.py index 627822b..724a5f5 100644 --- a/src/dve/core_engine/backends/implementations/duckdb/duckdb_helpers.py +++ b/src/dve/core_engine/backends/implementations/duckdb/duckdb_helpers.py @@ -7,7 +7,7 @@ from datetime import date, datetime, time from decimal import Decimal from pathlib import Path -from typing import Any, ClassVar, Union +from typing import Any, ClassVar, Literal, Union from urllib.parse import urlparse import duckdb.typing as ddbtyp @@ -125,8 +125,8 @@ def get_duckdb_type_from_annotation(type_annotation: Any) -> DuckDBPyType: 'optional' wrapper and return the inner type - A subclass of `typing.TypedDict` with values typed using supported types. This will parse the value types as Polars types and return a duckdb STRUCT. - - A dataclass or `pydantic.main.ModelMetaClass` with values typed using supported types. - This will parse the field types as Polars types and return a duckdb STRUCT. + - A dataclass or `pydantic.BaseModel` with values typed using supported types. + This will parse the field types as duckdb types and return a duckdb STRUCT. - Any supported type, with a `typing_extensions.Annotated` wrapper. Any `ClassVar` types within `TypedDict`s, dataclasses, or `pydantic` models will be @@ -135,6 +135,14 @@ def get_duckdb_type_from_annotation(type_annotation: Any) -> DuckDBPyType: """ type_origin = get_origin(type_annotation) + if type_origin is Literal: + ddb_types = [get_duckdb_type_from_annotation(type(t)) for t in get_args(type_annotation)] + if not ddb_types or not all(t == ddb_types[0] for t in ddb_types): + raise ValueError( + f"Unable to determine a single concrete type for Literal. Got {type_annotation!r}" + ) + return ddb_types[0] + # An `Optional` or `Union` type, check to ensure non-heterogenity. if type_origin is Union: python_type = _get_non_heterogenous_type(get_args(type_annotation)) diff --git a/src/dve/core_engine/backends/implementations/duckdb/readers/csv.py b/src/dve/core_engine/backends/implementations/duckdb/readers/csv.py index 17d7635..7d8201d 100644 --- a/src/dve/core_engine/backends/implementations/duckdb/readers/csv.py +++ b/src/dve/core_engine/backends/implementations/duckdb/readers/csv.py @@ -110,8 +110,8 @@ def read_to_relation( # pylint: disable=unused-argument } ddb_schema: dict[str, SQLType] = { - fld.name: str(get_duckdb_type_from_annotation(fld.annotation)) # type: ignore - for fld in schema.__fields__.values() + name: str(get_duckdb_type_from_annotation(fld.annotation)) # type: ignore + for name, fld in schema.model_fields.items() } reader_options["columns"] = ddb_schema @@ -154,8 +154,8 @@ def read_to_relation( # pylint: disable=unused-argument } polars_types = { - fld.name: get_polars_type_from_annotation(fld.annotation) # type: ignore - for fld in schema.__fields__.values() + name: get_polars_type_from_annotation(fld.annotation) # type: ignore + for name, fld in schema.model_fields.items() } reader_options["dtypes"] = polars_types diff --git a/src/dve/core_engine/backends/implementations/duckdb/readers/json.py b/src/dve/core_engine/backends/implementations/duckdb/readers/json.py index cf0fa82..e316593 100644 --- a/src/dve/core_engine/backends/implementations/duckdb/readers/json.py +++ b/src/dve/core_engine/backends/implementations/duckdb/readers/json.py @@ -48,8 +48,8 @@ def read_to_relation( # pylint: disable=unused-argument """Returns a relation object from the source json""" ddb_schema: dict[str, SQLType] = { - fld.name: str(get_duckdb_type_from_annotation(fld.annotation)) # type: ignore - for fld in schema.__fields__.values() + name: str(get_duckdb_type_from_annotation(fld.annotation)) # type: ignore + for name, fld in schema.model_fields.items() } return self.add_record_index( diff --git a/src/dve/core_engine/backends/implementations/duckdb/readers/xml.py b/src/dve/core_engine/backends/implementations/duckdb/readers/xml.py index c63e464..678047d 100644 --- a/src/dve/core_engine/backends/implementations/duckdb/readers/xml.py +++ b/src/dve/core_engine/backends/implementations/duckdb/readers/xml.py @@ -41,8 +41,8 @@ def read_to_relation(self, resource: URI, entity_name: str, schema: type[BaseMod ) polars_schema: dict[str, pl.DataType] = { # type: ignore - fld.name: get_polars_type_from_annotation(fld.annotation) - for fld in stringify_model(schema).__fields__.values() + name: get_polars_type_from_annotation(fld.annotation) + for name, fld in stringify_model(schema).model_fields.items() } _lazy_frame = self.add_record_index( diff --git a/src/dve/core_engine/backends/implementations/duckdb/rules.py b/src/dve/core_engine/backends/implementations/duckdb/rules.py index c93a6dd..debb8fe 100644 --- a/src/dve/core_engine/backends/implementations/duckdb/rules.py +++ b/src/dve/core_engine/backends/implementations/duckdb/rules.py @@ -152,7 +152,7 @@ def _add_cnst_field(rel: DuckDBPyRelation) -> tuple[str, DuckDBPyRelation]: group_pl = entity.pl().pivot( columns=[config.pivot_column], values=agg_cols, - index=(group_cols or [const_fld]), + index=(group_cols or [const_fld]), # pylint: disable=E0606 aggregate_function=config.agg_function, ) if const_fld in group_pl.columns: diff --git a/src/dve/core_engine/backends/implementations/spark/auditing.py b/src/dve/core_engine/backends/implementations/spark/auditing.py index c050a17..3f50721 100644 --- a/src/dve/core_engine/backends/implementations/spark/auditing.py +++ b/src/dve/core_engine/backends/implementations/spark/auditing.py @@ -116,7 +116,7 @@ def conv_to_records(self, recs: DataFrame) -> Iterable[AuditRecord]: def conv_to_entity(self, recs: list[AuditRecord]) -> DataFrame: """Convert the dataframe to an iterable of the related audit record""" return self._spark.createDataFrame( # type: ignore - [rec.dict() for rec in recs], schema=self.spark_schema + [rec.model_dump() for rec in recs], schema=self.spark_schema ) def add_records(self, records: Iterable[dict[str, Any]]): diff --git a/src/dve/core_engine/backends/implementations/spark/backend.py b/src/dve/core_engine/backends/implementations/spark/backend.py index 126e07a..abd1540 100644 --- a/src/dve/core_engine/backends/implementations/spark/backend.py +++ b/src/dve/core_engine/backends/implementations/spark/backend.py @@ -92,5 +92,5 @@ def write_entities_to_parquet( def convert_submission_info(self, submission_info: SubmissionInfo) -> DataFrame: return self.spark_session.createDataFrame( # type: ignore - [submission_info.dict()], schema=get_type_from_annotation(type(submission_info)) + [submission_info.model_dump()], schema=get_type_from_annotation(type(submission_info)) ) diff --git a/src/dve/core_engine/backends/implementations/spark/spark_helpers.py b/src/dve/core_engine/backends/implementations/spark/spark_helpers.py index ced985a..92703cc 100644 --- a/src/dve/core_engine/backends/implementations/spark/spark_helpers.py +++ b/src/dve/core_engine/backends/implementations/spark/spark_helpers.py @@ -12,11 +12,10 @@ from dataclasses import dataclass, is_dataclass from decimal import Decimal from functools import wraps -from typing import Any, ClassVar, Optional, TypeVar, Union, overload +from typing import Any, ClassVar, Literal, Optional, TypeVar, Union, overload from delta.exceptions import ConcurrentAppendException, DeltaConcurrentModificationException from pydantic import BaseModel -from pydantic.types import ConstrainedDecimal from pyspark.sql import DataFrame, Row, SparkSession from pyspark.sql import functions as sf from pyspark.sql import types as st @@ -49,6 +48,7 @@ """A wrapped function (Spark UDF) taking four args.""" +# TODO - lets see if we can bin this off as it's a bit overkill @dataclass(frozen=True) class DecimalConfig: """Configuration for a Python decimal to enable it to be mapped to a @@ -61,13 +61,13 @@ class DecimalConfig: """ - precision: int = 38 + max_digits: int = 38 """ The precision of the decimal. This is the total number of digits in the decimal. """ - scale: int = 18 + decimal_places: int = 18 """ The scale of the decimal. This is the number of digits to the right of the decimal point. @@ -75,10 +75,12 @@ class DecimalConfig: """ def __post_init__(self): - if not 0 < self.precision <= 38: - raise ValueError("Precision must be between 1 and 38 (inclusive)") - if not 0 <= self.scale <= self.precision: - raise ValueError("Scale must be between 0 and the precision (inclusive)") + if not 0 < self.max_digits <= 38: + raise ValueError("Max digits must be between 1 and 38 (inclusive)") + if not 0 <= self.decimal_places <= self.max_digits: + raise ValueError( + "Decimal Places must be between 0 and the specified number of digits (inclusive)" + ) DEFAULT_DECIMAL_CONFIG = DecimalConfig() @@ -93,7 +95,9 @@ def __post_init__(self): bytes: st.BinaryType(), dt.date: st.DateType(), dt.datetime: st.TimestampType(), - Decimal: st.DecimalType(DEFAULT_DECIMAL_CONFIG.precision, DEFAULT_DECIMAL_CONFIG.scale), + Decimal: st.DecimalType( + DEFAULT_DECIMAL_CONFIG.max_digits, DEFAULT_DECIMAL_CONFIG.decimal_places + ), } """A mapping of Python types to the equivalent Spark types.""" @@ -146,7 +150,7 @@ def get_type_from_annotation(type_annotation: Any) -> st.DataType: 'optional' wrapper and return the inner type (Spark types are all nullable). - A subclass of `typing.TypedDict` with values typed using supported types. This will parse the value types as Spark types and return a Spark `StructType`. - - A dataclass or `pydantic.main.ModelMetaClass` with values typed using supported types. + - A dataclass or `pydantic.BaseModel` with values typed using supported types. This will parse the field types as Spark types and return a Spark `StructType`. - Any supported type, with a `typing_extensions.Annotated` wrapper. - A `decimal.Decimal` wrapped with `typing_extensions.Annotated` with a `DecimalConfig` @@ -160,6 +164,14 @@ def get_type_from_annotation(type_annotation: Any) -> st.DataType: """ type_origin = get_origin(type_annotation) + if type_origin is Literal: + types = [get_type_from_annotation(type(t)) for t in get_args(type_annotation)] + if not types or not all(t == types[0] for t in types): + raise ValueError( + f"Unable to determine a single concrete type for Literal. Got {type_annotation!r}" + ) + return types[0] + # An `Optional` or `Union` type, check to ensure non-heterogenity. if type_origin is Union: python_type = _get_non_heterogenous_type(get_args(type_annotation)) @@ -176,13 +188,13 @@ def get_type_from_annotation(type_annotation: Any) -> st.DataType: if python_type is not Decimal: return get_type_from_annotation(python_type) - try: # Grab the decimal configuration from the list of other args. - configuration: DecimalConfig = next( - filter(lambda config: isinstance(config, DecimalConfig), other_args) - ) - except StopIteration: + config_options = [arg for arg in other_args if hasattr(arg, "max_digits")] + if config_options: + configuration = config_options[0] + else: configuration = DEFAULT_DECIMAL_CONFIG - return st.DecimalType(configuration.precision, configuration.scale) + + return st.DecimalType(configuration.max_digits, configuration.decimal_places) # Ensure that we have a concrete type at this point. if not isinstance(type_annotation, type): @@ -216,11 +228,6 @@ def get_type_from_annotation(type_annotation: Any) -> st.DataType: return st.StructType(fields) - if issubclass(type_annotation, ConstrainedDecimal): - precision = int(type_annotation.max_digits or 38) - scale = int(type_annotation.decimal_places or precision) - return st.DecimalType(precision, scale) - if type_annotation is list: raise ValueError( f"list must have type annotation (e.g. `list[str]`), got {type_annotation!r}" diff --git a/src/dve/core_engine/backends/implementations/spark/utilities.py b/src/dve/core_engine/backends/implementations/spark/utilities.py index 5eca158..399d355 100644 --- a/src/dve/core_engine/backends/implementations/spark/utilities.py +++ b/src/dve/core_engine/backends/implementations/spark/utilities.py @@ -110,7 +110,7 @@ class PydanticCompatibleJSONEncoder(JSONEncoder): def default(self, o: Any) -> Any: """Sets the format for given types for json encoding""" if isinstance(o, BaseModel): - return o.dict() + return o.model_dump() if isinstance(o, dt.date): return o.isoformat() return super().default(o) diff --git a/src/dve/core_engine/backends/metadata/contract.py b/src/dve/core_engine/backends/metadata/contract.py index 12beb41..e3eb0c0 100644 --- a/src/dve/core_engine/backends/metadata/contract.py +++ b/src/dve/core_engine/backends/metadata/contract.py @@ -2,7 +2,7 @@ from typing import Any -from pydantic import BaseModel, PrivateAttr, root_validator +from pydantic import BaseModel, PrivateAttr, model_validator from dve.core_engine.type_hints import EntityName, ReportingFields from dve.core_engine.validation import RowValidator @@ -44,10 +44,10 @@ def schemas(self) -> dict[EntityName, type[BaseModel]]: """The per-entity schemas, as pydantic models.""" if not self._schemas: for entity_name, validator in self.validators.items(): - self._schemas[entity_name] = validator.model # type: ignore - return self._schemas.copy() + self._schemas[entity_name] = validator.model # type: ignore # pylint: disable=E1137 + return self._schemas.copy() # pylint: disable=E1101 - @root_validator(allow_reuse=True) + @model_validator(mode="before") @classmethod def _ensure_entities_complete(cls, values: dict[str, dict[EntityName, Any]]): """Ensure the entities in 'readers' and 'validators' are the same.""" diff --git a/src/dve/core_engine/backends/metadata/reporting.py b/src/dve/core_engine/backends/metadata/reporting.py index 3989a13..6385f37 100644 --- a/src/dve/core_engine/backends/metadata/reporting.py +++ b/src/dve/core_engine/backends/metadata/reporting.py @@ -5,7 +5,7 @@ from collections.abc import Callable from typing import Any, ClassVar, Optional, Union -from pydantic import BaseModel, root_validator, validate_arguments +from pydantic import BaseModel, model_validator, validate_call from typing_extensions import Literal from dve.core_engine.templating import template_object @@ -124,8 +124,10 @@ def template( variables.update(local_variables) else: variables = local_variables - templated = template_object(self.dict(exclude=self.UNTEMPLATED_FIELDS), variables, "jinja") - templated.update(self.dict(include=self.UNTEMPLATED_FIELDS)) + templated = template_object( + self.model_dump(exclude=self.UNTEMPLATED_FIELDS), variables, "jinja" + ) + templated.update(self.model_dump(include=self.UNTEMPLATED_FIELDS)) return type_(**templated) @@ -269,7 +271,7 @@ class LegacyReportingConfig(BaseReportingConfig): legacy_is_informational: Optional[Union[bool, str]] = None """DEPRECATED: The legacy 'is_informational' flag.""" - @root_validator(allow_reuse=True, skip_on_failure=True) + @model_validator(mode="before") @classmethod def _ensure_only_one_reporting_config(cls, values: dict[str, Any]) -> dict[str, Any]: """Ensure only the modern or legacy location is populated.""" @@ -283,7 +285,7 @@ def _ensure_only_one_reporting_config(cls, values: dict[str, Any]) -> dict[str, ) return values - @root_validator(allow_reuse=True, skip_on_failure=True) + @model_validator(mode="before") @classmethod def _ensure_only_one_error_type_config(cls, values: dict[str, Any]) -> dict[str, Any]: """Ensure only the modern or legacy error type is populated.""" @@ -300,7 +302,7 @@ def _ensure_only_one_error_type_config(cls, values: dict[str, Any]) -> dict[str, return values @staticmethod - @validate_arguments + @validate_call def _convert_legacy_emit_value( failure_type: Literal["record", "submission", "integrity", "group"], is_informational: bool ) -> str: @@ -319,7 +321,7 @@ def _convert_legacy_emit_value( return emit @staticmethod - @validate_arguments + @validate_call def _convert_legacy_reporting_fields( error_location: Optional[str] = None, reporting_field: Union[str, list[str], None] = None ) -> Optional[str]: diff --git a/src/dve/core_engine/backends/metadata/rules.py b/src/dve/core_engine/backends/metadata/rules.py index 7bc0353..f3a6305 100644 --- a/src/dve/core_engine/backends/metadata/rules.py +++ b/src/dve/core_engine/backends/metadata/rules.py @@ -1,11 +1,13 @@ """Metadata classes for rule steps.""" +from __future__ import annotations + import warnings from abc import ABCMeta, abstractmethod from collections.abc import Iterator, Sequence from typing import Any, ClassVar, Optional, TypeVar, Union -from pydantic import BaseModel, Extra, Field, root_validator, validate_arguments, validator +from pydantic import BaseModel, Field, ValidationInfo, field_validator, model_validator from typing_extensions import Literal from dve.core_engine.backends.base.reference_data import ReferenceConfigUnion @@ -72,16 +74,15 @@ def __repr__(self) -> str: else: components.append(f"rule=Rule(name={self.rule!r}, ...)") - for key, value in self.dict(exclude={"rule"}).items(): + for key, value in self.model_dump(exclude={"rule"}).items(): components.append(f"{key}={value!r}") return f"ParentMetadata({', '.join(components)})" - class Config: # pylint: disable=too-few-public-methods - """`pydantic configuration options`""" - - frozen = True - extra = Extra.forbid + model_config = { + "frozen": True, + "extra": "allow", + } # pylint: disable=too-few-public-methods @@ -98,11 +99,10 @@ class AbstractStep(BaseModel, metaclass=ABCMeta): UNTEMPLATED_KEYS: ClassVar[set[str]] = {"id", "description", "parent"} """A set of aliases which are exempted from templating.""" - class Config: # pylint: disable=too-few-public-methods - """`pydantic configuration options`""" - - frozen = True - extra = Extra.forbid + model_config = { + "frozen": True, + "extra": "allow", + } def __repr_args__(self) -> Sequence[tuple[Optional[str], Any]]: # Exclude nulls from 'repr' for conciseness. @@ -149,7 +149,7 @@ def template( def __str__(self): # pydantic's default __str__ strips the model name. return super().__repr__() - @root_validator(pre=True) + @model_validator(mode="before") @classmethod def _warn_for_deprecated_aliases(cls, values: dict[str, JSONable]) -> dict[str, JSONable]: for deprecated_name, replacement in ( @@ -375,27 +375,19 @@ class Aggregation(BaseStep): agg_function: Optional[Alias] = None """The aggregate function to apply to the agg_columns (for duckdb backend)""" - @validator("pivot_values") + @field_validator("pivot_values") @classmethod - def _ensure_column_if_values( - cls, - value: Optional[Any], - values: dict[str, Any], - ): + def _ensure_column_if_values(cls, value: Optional[Any], info: ValidationInfo): """Ensure that `pivot_column` is not null if pivot values are provided.""" - if value and not values["pivot_column"]: + if value and not info.data["pivot_column"]: raise ValueError("`pivot_values` specified, but no `pivot_column`") return value - @validator("agg_function") + @field_validator("agg_function") @classmethod - def _ensure_column_if_function( - cls, - agg_function: Optional[Any], - values: dict[str, Any], - ): + def _ensure_column_if_function(cls, agg_function: Optional[Any], info: ValidationInfo): """Ensure that `pivot_column` is not null if pivot values are provided.""" - if agg_function and not values["agg_columns"]: + if agg_function and not info.data["agg_columns"]: raise ValueError("`agg_function` specified, but no `agg_columns`") return agg_function @@ -582,7 +574,7 @@ def __str__(self): # pydantic's default __str__ strips the model name. return super().__repr__() @classmethod - @validate_arguments + # @validate_call #TODO - removed for now as it's broken in pydantic v2 def from_step_list(cls, name: str, steps: list[Step]): """Load the rule from a single step list.""" pre_sync_steps: list[AbstractStep] = [] @@ -711,7 +703,7 @@ class RuleMetadata(BaseModel): """ - @root_validator() + @model_validator(mode="before") @classmethod def _ensure_locals_same_length_as_rules(cls, values: dict[str, list[Any]]): """Ensure that if 'local_variables' is provided, it's the same length as 'rules'.""" @@ -734,4 +726,4 @@ def __iter__(self) -> Iterator[tuple[Rule, TemplateVariables]]: # type: ignore yield from zip(self.rules, self.local_variables) -ParentMetadata.update_forward_refs() +ParentMetadata.model_rebuild() diff --git a/src/dve/core_engine/backends/readers/csv.py b/src/dve/core_engine/backends/readers/csv.py index edd6bf0..055458a 100644 --- a/src/dve/core_engine/backends/readers/csv.py +++ b/src/dve/core_engine/backends/readers/csv.py @@ -196,7 +196,7 @@ def read_to_py_iterator( if get_content_length(resource) == 0: raise EmptyFileError(f"File at {resource!r} is empty") - field_names = list(schema.__fields__.keys()) + field_names = list(schema.model_fields.keys()) with open_stream(resource, "r", self.encoding) as stream: reader = csv.DictReader( stream, @@ -223,8 +223,8 @@ def write_parquet( # type: ignore target_location = file_uri_to_local_path(target_location).as_posix() if schema: polars_schema: dict[str, pl.DataType] = { # type: ignore - fld.name: get_polars_type_from_annotation(fld.annotation) - for fld in stringify_model(schema).__fields__.values() + name: get_polars_type_from_annotation(fld.annotation) + for name, fld in stringify_model(schema).model_fields.items() } polars_schema[RECORD_INDEX_COLUMN_NAME] = get_polars_type_from_annotation(int) diff --git a/src/dve/core_engine/backends/readers/utilities.py b/src/dve/core_engine/backends/readers/utilities.py index 642c0b2..6b103b6 100644 --- a/src/dve/core_engine/backends/readers/utilities.py +++ b/src/dve/core_engine/backends/readers/utilities.py @@ -17,5 +17,5 @@ def check_csv_header_expected( """Check the header of a CSV matches the expected fields""" with open_stream(resource) as fle: header_fields = fle.readline().rstrip().replace(quote_char, "").split(delimiter) - expected_fields = expected_schema.__fields__.keys() + expected_fields = expected_schema.model_fields.keys() return set(expected_fields).difference(header_fields) diff --git a/src/dve/core_engine/backends/readers/xml.py b/src/dve/core_engine/backends/readers/xml.py index 4620402..71a076e 100644 --- a/src/dve/core_engine/backends/readers/xml.py +++ b/src/dve/core_engine/backends/readers/xml.py @@ -13,7 +13,12 @@ from dve.core_engine.backends.base.reader import BaseFileReader from dve.core_engine.backends.exceptions import EmptyFileError from dve.core_engine.backends.readers.xml_linting import run_xmllint -from dve.core_engine.backends.utilities import get_polars_type_from_annotation, stringify_model +from dve.core_engine.backends.utilities import ( + get_polars_type_from_annotation, + is_field_complex, + is_type_complex, + stringify_model, +) from dve.core_engine.constants import RECORD_INDEX_COLUMN_NAME from dve.core_engine.loggers import get_logger from dve.core_engine.message import FeedbackMessage @@ -43,6 +48,15 @@ def _strip_annotated(annotation: Any) -> Any: return get_args(annotation)[0] +def _strip_optional(annotation: Any) -> Any: + """Strip Optional type from a type""" + if hasattr(annotation, "_name"): + if annotation._name == "Optional": # pylint: disable=W0212 + python_type, _default = get_args(annotation) + return python_type + return annotation + + def create_template_row(schema: type[BaseModel]) -> dict[str, Any]: """Create a template row from a schema. A template row is essentially the shape of the record that would be populated by the reader (i.e. contains @@ -51,10 +65,10 @@ def create_template_row(schema: type[BaseModel]) -> dict[str, Any]: """ template_row: dict[str, Any] = {} - for field_name, model_field_def in schema.__fields__.items(): - field_type = _strip_annotated(model_field_def.annotation) + for field_name, model_field_def in schema.model_fields.items(): # type: ignore + field_type = _strip_optional(_strip_annotated(model_field_def.annotation)) - if not model_field_def.is_complex(): + if not is_type_complex(field_type): template_row[field_name] = None continue @@ -70,8 +84,8 @@ def create_template_row(schema: type[BaseModel]) -> dict[str, Any]: # This is a quick and dirty hack to avoid implementing our own logic # to check complex types... - list_type_field_spec = create_model("", lt=(list_type, ...)).__fields__["lt"] - if not list_type_field_spec.is_complex(): + list_type_field_spec = create_model("", lt=(list_type, ...)).model_fields["lt"] + if not is_field_complex(list_type_field_spec): template_row[field_name] = [None] continue @@ -329,8 +343,8 @@ def write_parquet( # type: ignore target_location = file_uri_to_local_path(target_location).as_posix() if schema: polars_schema: dict[str, pl.DataType] = { # type: ignore - fld.name: get_polars_type_from_annotation(fld.type_) - for fld in stringify_model(schema).__fields__.values() + name: get_polars_type_from_annotation(fld.type_) + for name, fld in stringify_model(schema).model_fields.items() } polars_schema[RECORD_INDEX_COLUMN_NAME] = get_polars_type_from_annotation(int) pl.LazyFrame(data=entity, schema=polars_schema).sink_parquet( diff --git a/src/dve/core_engine/backends/utilities.py b/src/dve/core_engine/backends/utilities.py index d94d0bf..6b8c5bc 100644 --- a/src/dve/core_engine/backends/utilities.py +++ b/src/dve/core_engine/backends/utilities.py @@ -4,11 +4,12 @@ from dataclasses import is_dataclass from datetime import date, datetime, time from decimal import Decimal -from typing import Any, ClassVar, GenericAlias, Union # type: ignore +from typing import Any, ClassVar, GenericAlias, Literal, Union # type: ignore import polars as pl # type: ignore from polars.datatypes.classes import DataTypeClass as PolarsType from pydantic import BaseModel, create_model +from pydantic.fields import FieldInfo from dve.core_engine.backends.base.utilities import _get_non_heterogenous_type from dve.core_engine.constants import RECORD_INDEX_COLUMN_NAME @@ -38,6 +39,26 @@ """A mapping of Python types to the equivalent Polars types.""" +def is_type_complex(type_: Any) -> bool: + """Check whether a type is a complex type or not.""" + if type_ in (str, int, float, bool, bytes): + return False + + if type_ in (date, datetime, time, Decimal): + return False + + return True + + +def is_field_complex(field: FieldInfo) -> bool: + """ + Replacement function for Pydantic v1 `is_complex` check provided + by the v1 ModelField object. + """ + type_annotation = field.annotation + return is_type_complex(type_annotation) + + def stringify_type(type_: Union[type, GenericAlias]) -> type: """Stringify an individual type.""" if isinstance(type_, type) and not isinstance( @@ -46,13 +67,13 @@ def stringify_type(type_: Union[type, GenericAlias]) -> type: if issubclass(type_, BaseModel): return stringify_model(type_) - is_complex = create_model("", t=(type_, ...)).__fields__["t"].is_complex() + is_complex = is_field_complex(create_model("", t=(type_, ...)).model_fields["t"]) if not is_complex: # A non-container type, return string. return str origin = get_origin(type_) if origin is None: # A non-generic container type, return as-is - return type_ + return type_ # type: ignore type_args = get_args(type_) if not type_args: @@ -68,9 +89,9 @@ def stringify_type(type_: Union[type, GenericAlias]) -> type: def stringify_model(model: type[BaseModel]) -> type[BaseModel]: """Stringify a `pydantic` model.""" fields = {} - for field_name, field in model.__fields__.items(): - fields[field_name] = (stringify_type(field.annotation), ...) - return create_model(model.__name__, **fields) # type: ignore + for field_name, field in model.model_fields.items(): + fields[field_name] = (stringify_type(field.annotation), ...) # type: ignore + return create_model(model.__class__.__name__, **fields) # type: ignore def dedup_messages(messages: Messages) -> Messages: @@ -106,7 +127,7 @@ def get_polars_type_from_annotation(type_annotation: Any) -> PolarsType: 'optional' wrapper and return the inner type - A subclass of `typing.TypedDict` with values typed using supported types. This will parse the value types as Polars types and return a Polars Struct. - - A dataclass or `pydantic.main.ModelMetaClass` with values typed using supported types. + - A dataclass or `pydantic.BaseModel` with values typed using supported types. This will parse the field types as Polars types and return a Polars Struct. - Any supported type, with a `typing_extensions.Annotated` wrapper. - A `decimal.Decimal` wrapped with `typing_extensions.Annotated` with a `DecimalConfig` @@ -120,6 +141,15 @@ def get_polars_type_from_annotation(type_annotation: Any) -> PolarsType: """ type_origin = get_origin(type_annotation) + if type_origin is Literal: + # TODO - look at using _get_non_heterogenous_type instead? + polars_types = [get_polars_type_from_annotation(type(t)) for t in get_args(type_annotation)] + if not polars_types or not all(t == polars_types[0] for t in polars_types): + raise ValueError( + f"Unable to determine a single concrete type for Literal. Got {type_annotation!r}" + ) + return polars_types[0] + # An `Optional` or `Union` type, check to ensure non-heterogenity. if type_origin is Union: python_type = _get_non_heterogenous_type(get_args(type_annotation)) diff --git a/src/dve/core_engine/configuration/v1/__init__.py b/src/dve/core_engine/configuration/v1/__init__.py index 89174be..959596f 100644 --- a/src/dve/core_engine/configuration/v1/__init__.py +++ b/src/dve/core_engine/configuration/v1/__init__.py @@ -3,8 +3,8 @@ import json from typing import Any, Optional, Union -from pydantic import BaseModel, Field, PrivateAttr, validate_arguments -from typing_extensions import Annotated, Literal +from pydantic import BaseModel, Field, PrivateAttr, validate_call +from typing_extensions import Literal from dve.core_engine.backends.base.reference_data import ReferenceConfig, ReferenceConfigUnion from dve.core_engine.backends.metadata.contract import DataContractMetadata, ReaderConfig @@ -38,7 +38,7 @@ FieldName = str """The name of a field within a model/schema.""" -TypeOrDef = Union[ +TypeOrDef = Union[ # pylint: disable=C0103 TypeName, "_CallableTypeDefinition", "_ModelTypeDefinition", "_TypeAliasDefinition" ] """The name or definition of a type.""" @@ -178,10 +178,10 @@ class V1EngineConfig(BaseEngineConfig): ) """Rule store rules from the loaded rule stores.""" - @validate_arguments + @validate_call def _update_rule_store(self, rule_store: dict[RuleName, BusinessComponentSpecConfigUnion]): """Update the rule store rules to add/override the rules from the new store.""" - self._rule_store_rules.update(rule_store) + self._rule_store_rules.update(rule_store) # pylint: disable=E1101 def _load_rule_store(self, uri: URI): """Load a JSON rule store from the provided URI and update the stored @@ -198,7 +198,7 @@ def _load_rule_store(self, uri: URI): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) uri_prefix = self.location.rsplit("/", 1)[0] - for rule_store_config in self.transformations.rule_stores: + for rule_store_config in self.transformations.rule_stores: # pylint: disable=E1101 uri = joinuri(uri_prefix, rule_store_config.filename) self._load_rule_store(uri) @@ -281,7 +281,9 @@ def _load_rules_and_vars(self) -> tuple[list[Rule], list[TemplateVariables]]: rules, local_variable_list = [], [] added_rules: set[RuleName] = set() - for index, complex_rule_config in enumerate(self.transformations.complex_rules): + for index, complex_rule_config in enumerate( + self.transformations.complex_rules # pylint: disable=E1101 + ): rule, local_params, deps = self._resolve_business_rule(complex_rule_config) missing_rules = deps - added_rules if missing_rules: @@ -295,9 +297,9 @@ def _load_rules_and_vars(self) -> tuple[list[Rule], list[TemplateVariables]]: rule, local_params = self._create_rule( name="root", - rules=self.transformations.rules, - filters=self.transformations.filters, - post_filter_rules=self.transformations.post_filter_rules, + rules=self.transformations.rules, # pylint: disable=E1101 + filters=self.transformations.filters, # pylint: disable=E1101 + post_filter_rules=self.transformations.post_filter_rules, # pylint: disable=E1101 ) rules.append(rule) local_variable_list.append(local_params) @@ -309,7 +311,7 @@ def get_contract_metadata(self) -> DataContractMetadata: validators = {} reporting_fields = {} - contract_dict = self.contract.dict() + contract_dict = self.contract.model_dump() error_info = {} if self.contract.error_details: error_info = self.load_error_message_info(self.contract.error_details) @@ -338,7 +340,7 @@ def load_error_message_info(self, uri): def get_reference_data_config(self) -> dict[EntityName, ReferenceConfig]: # type: ignore """Gets the reference data configuration from the transformations""" - return self.transformations.reference_data + return self.transformations.reference_data # pylint: disable=E1101 def get_rule_metadata(self) -> RuleMetadata: """Gets the rule metadata from the Engine configuration""" @@ -346,6 +348,6 @@ def get_rule_metadata(self) -> RuleMetadata: return RuleMetadata( rules=rules, local_variables=local_variables, - global_variables=self.transformations.parameters, + global_variables=self.transformations.parameters, # pylint: disable=E1101 reference_data_config=self.get_reference_data_config(), ) diff --git a/src/dve/core_engine/configuration/v1/steps.py b/src/dve/core_engine/configuration/v1/steps.py index f795c8c..6b30a4a 100644 --- a/src/dve/core_engine/configuration/v1/steps.py +++ b/src/dve/core_engine/configuration/v1/steps.py @@ -10,9 +10,9 @@ # pylint: disable=missing-class-docstring from abc import ABC, abstractmethod -from typing import Any, Optional, Union +from typing import Optional, Union -from pydantic import BaseModel, Extra, Field, validator +from pydantic import BaseModel, Field, ValidationInfo, field_validator from typing_extensions import Annotated, Literal from dve.core_engine.backends.metadata.rules import ( @@ -40,10 +40,9 @@ class ConfigStep(BaseModel, ABC): """The parent for the config steps.""" - class Config: # pylint: disable=too-few-public-methods - """Config class for dynamically generated pydantic models""" - - extra = Extra.forbid + model_config = { + "extra": "forbid", + } name: Optional[str] = None """The 'name' of the rule. This is mapped to an ID in the entity.""" @@ -107,10 +106,10 @@ class GroupByConfig(ConfigStep): pivot_values: Optional[list[str]] = None agg_columns: MultipleExpressions - @validator("pivot_values") + @field_validator("pivot_values") @classmethod - def _ensure_no_values_if_not_column(cls, value: Optional[str], values: dict[str, Any]): - if value and not values["pivot_column"]: + def _ensure_no_values_if_not_column(cls, value: Optional[str], info: ValidationInfo): + if value and not info.data.get("pivot_column"): raise ValueError("Cannot provide 'pivot_values' if no 'pivot_column'") return value diff --git a/src/dve/core_engine/engine.py b/src/dve/core_engine/engine.py index c5d1ba9..a7931ad 100644 --- a/src/dve/core_engine/engine.py +++ b/src/dve/core_engine/engine.py @@ -4,9 +4,17 @@ import logging from pathlib import Path from types import TracebackType -from typing import Any, Optional, Union - -from pydantic import BaseModel, Field, PrivateAttr, validate_arguments, validator +from typing import Annotated, Optional, Union + +from pydantic import ( + BaseModel, + ConfigDict, + Field, + PrivateAttr, + ValidationInfo, + field_validator, + validate_call, +) from pydantic.types import FilePath from pyspark.sql import SparkSession @@ -26,11 +34,7 @@ class CoreEngine(BaseModel): """The core engine implementation for the data validation engine.""" - class Config: # pylint: disable=too-few-public-methods - """`pydantic` configuration options.""" - - arbitrary_types_allowed = True - validate_assignment = True + model_config = {"arbitrary_types_allowed": True, "validate_assignment": True} backend_config: BaseEngineConfig """The backend configuration for the given run.""" @@ -52,16 +56,20 @@ class Config: # pylint: disable=too-few-public-methods Data will be chunked to parquet in this directory after being read, and written here before filters are applied. - """ - backend: BaseBackend = None # type: ignore + # TODO - recommended to not use validate_default like this, but for now will use as replacement to always=True # pylint: disable=C0301 + # see https://pydantic.dev/docs/validation/latest/get-started/migration/#validator-and-root_validator-are-deprecated # pylint: disable=C0301 + backend: Annotated[Optional[BaseBackend], Field(default=None, validate_default=True)] """The backend to use to process the files.""" + debug: bool = False """Indication of if this run is in debug mode.""" - @validator("cache_prefix_uri", "output_prefix_uri", allow_reuse=True, pre=True) + @field_validator("cache_prefix_uri", "output_prefix_uri", mode="before") # pylint: disable=E0213 - def _validate_prefix_uri(cls, location: Optional[Location]) -> Optional[URI]: + def _validate_prefix_uri( + cls, location: Optional[Location], info: ValidationInfo # pylint: disable=W0613 + ) -> Optional[URI]: """Ensure we support the cache prefix scheme.""" if location is None: return None @@ -71,25 +79,25 @@ def __init__(self, *args, **kwargs): # pylint: disable=W0235 super().__init__(*args, **kwargs) - @validator("backend", always=True) + @field_validator("backend") @classmethod - def _ensure_backend(cls, value: Optional[BaseBackend], values: dict[str, Any]) -> BaseBackend: + def _ensure_backend(cls, value: Optional[BaseBackend], info: ValidationInfo) -> BaseBackend: """Ensure a default backend is created if a backend is not specified.""" if value is not None: return value - main_logger = values.get("main_log") + main_logger = info.data.get("main_log") if main_logger is None: - return SparkBackend(dataset_config_uri=values.get("dataset_config_uri")) + return SparkBackend(dataset_config_uri=info.data.get("dataset_config_uri")) return SparkBackend( - dataset_config_uri=values.get("dataset_config_uri"), + dataset_config_uri=info.data.get("dataset_config_uri"), logger=get_child_logger( ".".join((SparkBackend.__module__, SparkBackend.__name__)), main_logger ), ) @classmethod - @validate_arguments(config={"arbitrary_types_allowed": True}) + @validate_call(config=ConfigDict(arbitrary_types_allowed=True)) def build( cls, dataset_config_path: Union[FilePath, URI], @@ -137,7 +145,7 @@ def build( debug=debug, **kwargs, ) - self.main_log.info(f"Output path: {self.output_prefix_uri!r}") + self.main_log.info(f"Output path: {self.output_prefix_uri!r}") # pylint: disable=E1101 return self @classmethod @@ -152,16 +160,16 @@ def build_from_model(cls, model_str: JSONstring): """ main_log = get_logger("CoreEngine") main_log.info("Initalise from model...") - return cls.build(**EngineRunValidation(**json.loads(model_str)).dict()) + return cls.build(**EngineRunValidation(**json.loads(model_str)).model_dump()) def __enter__(self) -> "CoreEngine": - self.main_log.info("Entering pipeline context.") + self.main_log.info("Entering pipeline context.") # pylint: disable=E1101 if self._cache_dir is not None: raise ValueError("Pipeline already within context") self._cache_dir = TemporaryPrefix(self.cache_prefix_uri) self._cache_dir.__enter__() - self.main_log.info(f"Pipeline will cache to {self.cache_prefix!r}") + self.main_log.info(f"Pipeline will cache to {self.cache_prefix!r}") # pylint: disable=E1101 return self def __exit__( @@ -170,14 +178,16 @@ def __exit__( exc_value: Optional[Exception], traceback: Optional[TracebackType], ) -> None: - self.main_log.info(f"Exiting pipeline context, clearing {self.cache_prefix!r}") + self.main_log.info( # pylint: disable=E1101 + f"Exiting pipeline context, clearing {self.cache_prefix!r}" + ) cache_dir = self._cache_dir self._cache_dir = None if cache_dir is not None: cache_dir.__exit__(exc_type, exc_value, traceback) - self.main_log.info("Cleared cache.") + self.main_log.info("Cleared cache.") # pylint: disable=E1101 @property def cache_prefix(self) -> URI: @@ -198,17 +208,23 @@ def _write_entity_outputs(self, entities: SparkEntities) -> SparkEntities: """ output_entities = {} - self.main_log.info(f"Writing entities to the output location: {self.output_prefix_uri}") + self.main_log.info( # pylint: disable=E1101 + f"Writing entities to the output location: {self.output_prefix_uri}" + ) for entity_name, entity in entities.items(): entity = entity.drop(RECORD_INDEX_COLUMN_NAME) - self.main_log.info(f"Entity: {entity_name} {type(entity)}") + self.main_log.info(f"Entity: {entity_name} {type(entity)}") # pylint: disable=E1101 output_uri = joinuri(self.output_prefix_uri, entity_name) if get_resource_exists(output_uri): - self.main_log.info(f"{output_uri} already exists - will be overwritten") + self.main_log.info( # pylint: disable=E1101 + f"{output_uri} already exists - will be overwritten" + ) - self.main_log.info(f"+ Writing parquet output to {output_uri!r}") + self.main_log.info( # pylint: disable=E1101 + f"+ Writing parquet output to {output_uri!r}" + ) entity.write.mode("overwrite").parquet(output_uri) spark_session = SparkSession.builder.getOrCreate() output_entities[entity_name] = spark_session.read.format("parquet").load( @@ -228,7 +244,7 @@ def _write_outputs(self, entities: SparkEntities) -> SparkEntities: def _show_available_entities(self, entities: SparkEntities, *, verbose: bool = False) -> None: """Print current entities.""" - self.main_log.info("Displaying available dataframes in this run:") + self.main_log.info("Displaying available dataframes in this run:") # pylint: disable=E1101 for entity_name, entity in entities.items(): # FIXME: Currently a print statement because log messages @@ -255,11 +271,16 @@ def run_pipeline( references should be valid after the pipeline context exits. """ - entities, errors_uri = self.backend.process_legacy( - self.output_prefix_uri, - entity_locations, - self.backend_config.get_contract_metadata(), - self.backend_config.get_rule_metadata(), - submission_info, + if self.backend: + entities, errors_uri = self.backend.process_legacy( + self.output_prefix_uri, + entity_locations, + self.backend_config.get_contract_metadata(), + self.backend_config.get_rule_metadata(), + submission_info, + ) + return self._write_outputs(entities), errors_uri + + raise AttributeError( + "Backend implementation not defined. Cannot run the pipeline without a defined backend. Choose DuckDB or Spark" # pylint: disable=C0301 ) - return self._write_outputs(entities), errors_uri diff --git a/src/dve/core_engine/loggers.py b/src/dve/core_engine/loggers.py index 036c3d2..6c9b3aa 100644 --- a/src/dve/core_engine/loggers.py +++ b/src/dve/core_engine/loggers.py @@ -18,7 +18,7 @@ def filter(self, record): class UTCFormatter(logging.Formatter): # pragma: no cover """A formatter with timestamps in the UTC timezone.""" - converter = time.gmtime + converter = time.gmtime # type: ignore def get_default_handler() -> logging.Handler: # pragma: no cover diff --git a/src/dve/core_engine/message.py b/src/dve/core_engine/message.py index 627ae3a..2980990 100644 --- a/src/dve/core_engine/message.py +++ b/src/dve/core_engine/message.py @@ -10,7 +10,7 @@ from functools import reduce from typing import Any, ClassVar, Optional, Union -from pydantic import BaseModel, ValidationError, validator +from pydantic import BaseModel, ConfigDict, ValidationError, ValidationInfo, field_validator from pydantic.dataclasses import dataclass from dve.core_engine.constants import CONTRACT_ERROR_VALUE_FIELD_NAME, RECORD_INDEX_COLUMN_NAME @@ -83,12 +83,6 @@ def extract_error_value(records, error_location): """ -class Config: # pylint: disable=too-few-public-methods - """`pydantic` configuration options.""" - - arbitrary_types_allowed = True - - # pylint: disable=R0902 @dataclass class UserMessage: @@ -130,7 +124,7 @@ def is_critical(self) -> bool: return self.FailureType == "integrity" -@dataclass(config=Config, eq=True) +@dataclass(config=ConfigDict(arbitrary_types_allowed=True), eq=True) class FeedbackMessage: # pylint: disable=too-many-instance-attributes """Information which affects processing and needs to be feeded back.""" @@ -195,9 +189,11 @@ class FeedbackMessage: # pylint: disable=too-many-instance-attributes ] """The header that should be written to CSV.""" - @validator("reporting_field") + @field_validator("reporting_field") # pylint: disable=no-self-argument - def _split_reporting_field(cls, value) -> Union[list[str], str, None]: + def _split_reporting_field( + cls, value: Optional[str | list[str]], info: ValidationInfo # pylint: disable=W0613 + ) -> Union[list[str], str, None]: if isinstance(value, list): return value if isinstance(value, str): @@ -210,9 +206,11 @@ def _split_reporting_field(cls, value) -> Union[list[str], str, None]: return value return None - @validator("error_location", pre=True) + @field_validator("error_location", mode="before") # pylint: disable=no-self-argument - def _validate_error_location(cls, value: Any) -> Optional[str]: + def _validate_error_location( + cls, value: Optional[str], info: ValidationInfo # pylint: disable=W0613 + ) -> Optional[str]: """Format error location to a string.""" if value is None: return None # pragma: no cover @@ -247,7 +245,8 @@ def from_pydantic_error( messages: Messages = [] for error_dict in error.errors(): error_type = error_dict["type"] - if "none.not_allowed" in error_type or "value_error.missing" in error_type: + _input = error_dict["input"] + if "missing" in error_type or _input is None: category = "Blank" else: category = "Bad value" @@ -267,6 +266,8 @@ def from_pydantic_error( is_informational = False if error_code.endswith("warning"): is_informational = True + # TODO - this should copy the default error detail and then update with any custom codes defined in error_details pylint: disable=C0301 + # TODO - to ensure that user does not need to define all custom details (i.e. bad value custom, blank default) pylint: disable=C0301 error_detail: DataContractErrorDetail = error_details.get( # type: ignore error_field, DEFAULT_ERROR_DETAIL ).get(category) diff --git a/src/dve/core_engine/models.py b/src/dve/core_engine/models.py index 09fcbb3..1d84bab 100644 --- a/src/dve/core_engine/models.py +++ b/src/dve/core_engine/models.py @@ -11,7 +11,15 @@ from pathlib import Path from typing import Any, Optional -from pydantic import UUID4, BaseModel, Field, FilePath, root_validator, validator +from pydantic import ( + UUID4, + BaseModel, + Field, + FilePath, + ValidationInfo, + field_validator, + model_validator, +) from dve.core_engine.backends.metadata.contract import ReaderConfig from dve.core_engine.type_hints import EntityName, ProcessingStatus, SubmissionResult @@ -26,16 +34,17 @@ class AuditRecord(BaseModel): submission_id: str """Unique id of the submission""" + # todo - why bother supplying a date_updated here when it gets overwritten by time? Only works if you supply both values # pylint: disable=C0301 date_updated: Optional[dt.date] = None """The date the record was added to the table""" time_updated: Optional[dt.datetime] = Field(default_factory=dt.datetime.now) """The timestamp the record was added to the table""" - @root_validator(allow_reuse=True) - def populate_date_updated(cls, values): # pylint: disable=no-self-argument + @model_validator(mode="after") + def populate_date_updated(self): """Add date_updated from time_updated value""" - values["date_updated"] = values["time_updated"].date() - return values + self.date_updated = self.time_updated.date() # pylint: disable=E1101 + return self class SubmissionInfoMismatchWarning(UserWarning): @@ -64,8 +73,10 @@ class SubmissionInfo(AuditRecord): datetime_received: Optional[dt.datetime] = None # type: ignore """The datetime the file was received.""" - @validator("file_extension") - def _ensure_just_file_stem(cls, extension: str): # pylint: disable=no-self-argument + @field_validator("file_extension") + def _ensure_just_file_stem( + cls, extension: str, info: ValidationInfo # pylint: disable=W0613 + ): # pylint: disable=no-self-argument return extension.rsplit(".", 1)[-1] @property @@ -95,8 +106,8 @@ def __eq__(self, other: object) -> bool: if not isinstance(other, SubmissionInfo): raise NotImplementedError("Unable to determine equality if not a SubmissionInfo object") _exclude = ["date_updated", "time_updated"] - return {k: v for k, v in self.dict().items() if k not in _exclude} == { - k: v for k, v in other.dict().items() if k not in _exclude + return {k: v for k, v in self.model_dump().items() if k not in _exclude} == { + k: v for k, v in other.model_dump().items() if k not in _exclude } @@ -116,8 +127,8 @@ def __eq__(self, other: object) -> bool: "Unable to determine equality if not a SubmissionStatisticsRecord object" ) # pylint: disable=line-too-long _exclude = ["date_updated", "time_updated"] - return {k: v for k, v in self.dict().items() if k not in _exclude} == { - k: v for k, v in other.dict().items() if k not in _exclude + return {k: v for k, v in self.model_dump().items() if k not in _exclude} == { + k: v for k, v in other.model_dump().items() if k not in _exclude } @@ -139,9 +150,9 @@ class ProcessingStatusRecord(AuditRecord): processing_status: ProcessingStatus """The processing status of the submission""" - job_run_id: Optional[int] + job_run_id: Optional[int] = None """The run id of the databricks job used to process the submission""" - submission_result: Optional[SubmissionResult] + submission_result: Optional[SubmissionResult] = None """Whether the file validation was a success or failure""" @@ -156,9 +167,9 @@ class EngineRun(BaseModel): # TODO: What if we want to set an alt/override output prefix # and not have the submission_id appended to it? - @validator("output_prefix") - def _set_output_path(cls, prefix, values: dict): # pylint: disable=E0213 - v_id = values.get("submission_id") + @field_validator("output_prefix") + def _set_output_path(cls, prefix, info: ValidationInfo): # pylint: disable=E0213 + v_id = info.data.get("submission_id") if v_id: return os.path.join(prefix, str(v_id)) return prefix @@ -181,8 +192,11 @@ class ConcreteEntity(EntitySpecification, arbitrary_types_allowed=True): """An optional key field to use for the entity.""" reporting_fields: Optional[list[str]] = None - @validator("reporting_fields", pre=True) - def _ensure_list(cls, value: Optional[str]) -> Optional[list[str]]: # pylint: disable=E0213 + @field_validator("reporting_fields", mode="before") + @classmethod + def _ensure_list( + cls, value: Optional[str], info: ValidationInfo # pylint: disable=W0613 + ) -> Optional[list[str]]: """Ensure the reporting fields are a list.""" if value is None: return None diff --git a/src/dve/core_engine/type_hints.py b/src/dve/core_engine/type_hints.py index 3112e28..154ada6 100644 --- a/src/dve/core_engine/type_hints.py +++ b/src/dve/core_engine/type_hints.py @@ -5,7 +5,7 @@ from multiprocessing import Queue as ProcessQueue from pathlib import Path from queue import Queue as ThreadQueue -from typing import TYPE_CHECKING, Any, List, Optional, TypeVar, Union # pylint: disable=W1901 +from typing import TYPE_CHECKING, Any, List, Optional, TypeVar, Union from pyspark.sql import DataFrame from pyspark.sql.types import StructType diff --git a/src/dve/core_engine/validation.py b/src/dve/core_engine/validation.py index f62309b..05f2e2b 100644 --- a/src/dve/core_engine/validation.py +++ b/src/dve/core_engine/validation.py @@ -6,8 +6,7 @@ from typing import Optional from pyarrow.lib import RecordBatch # type: ignore -from pydantic import ValidationError -from pydantic.main import ModelMetaclass +from pydantic import BaseModel, ValidationError from dve.core_engine.message import DEFAULT_ERROR_DETAIL, DataContractErrorDetail, FeedbackMessage from dve.core_engine.type_hints import ContractContents, EntityName, ErrorCategory, Messages, Record @@ -36,7 +35,7 @@ def __init__( self._model_definition = model_definition self._validators = validators self.entity_name = entity_name - self._model: Optional[ModelMetaclass] = None + self._model: Optional[BaseModel] = None self._error_info = error_info or {} self._error_details: Optional[ dict[FieldName, dict[ErrorCategory, DataContractErrorDetail]] @@ -48,7 +47,7 @@ def __reduce__(self): # Don't attempt to pickle Pydantic models. return super().__reduce__() @property - def model(self) -> ModelMetaclass: + def model(self) -> BaseModel: """The loaded pydantic model for the entity.""" if not self._model: models = JSONtoPyd(self._model_definition).generate_models( @@ -83,7 +82,7 @@ def __call__(self, record: Record) -> tuple[Optional[Record], Messages]: messages: Messages = [] try: # pylint: disable=not-callable - validated: Record = self.model(**record).dict() + validated: Record = self.model(**record).model_dump() # type: ignore except ValidationError as err: # we still want to report warnings # when a record is invalid diff --git a/src/dve/metadata_parser/domain_types.py b/src/dve/metadata_parser/domain_types.py index 6e102ea..3d7bc3c 100644 --- a/src/dve/metadata_parser/domain_types.py +++ b/src/dve/metadata_parser/domain_types.py @@ -1,15 +1,18 @@ """Domain specific type definitions for use in validators.""" # pylint: disable=too-few-public-methods +# pylint: disable=W0613 + import datetime as dt import itertools import re import warnings -from collections.abc import Iterator, Sequence +from collections.abc import Sequence from functools import lru_cache -from typing import ClassVar, Optional, TypeVar, Union +from typing import Any, ClassVar, Optional, TypeVar, Union -from pydantic import fields, types, validate_arguments +from pydantic import GetCoreSchemaHandler, types, validate_call +from pydantic_core import CoreSchema, core_schema from typing_extensions import Literal from dve.metadata_parser import exc @@ -32,16 +35,16 @@ POSTCODE_REGEX = re.compile(r"\A[a-zA-Z]{1,2}\d([a-zA-Z]?|\d?)\s\d[a-zA-Z]{2}\Z") -class _SimpleRegexValidator(types.ConstrainedStr): +class _SimpleRegexValidator: """A basic regex-validated type.""" - regex: re.Pattern + pattern: re.Pattern """A regex pattern used to validate the string.""" strip_whitespace: bool = True """Whether to strip the whitespace from the string.""" -class NHSNumber(types.ConstrainedStr): +class NHSNumber(str): """A constrained string which validates an NHS number. ### Validation criteria @@ -98,7 +101,7 @@ class NHSNumber(types.ConstrainedStr): warn_on_test_numbers = True @classmethod - def _warn_for_possible_invalid_number(cls, nhs_number: str, loc: str) -> None: + def _warn_for_possible_invalid_number(cls, nhs_number: str) -> None: """Emit warnings for possible invalid NHS numbers.""" reason = None @@ -111,10 +114,10 @@ def _warn_for_possible_invalid_number(cls, nhs_number: str, loc: str) -> None: reason = "NHS number is a palindrome: this indicates a test number" if reason: - warnings.warn(exc.LocWarning(f"NHS number possibly invalid ({reason})", loc)) + warnings.warn(exc.LocWarning(f"NHS number possibly invalid ({reason})")) @staticmethod - def ensure_format(nhs_number: Optional[str]) -> str: + def ensure_format(nhs_number: Optional[str | int]) -> str: """Coerce an NHS number string to the correct format, raising an error if coersion fails. @@ -139,7 +142,7 @@ def confirm_checksum_validates(nhs_number: str) -> bool: return check == int(check_digit) @classmethod - def check_validates(cls, value: Optional[str]) -> bool: + def check_validates(cls, value: str) -> bool: """Check whether an NHS number is valid, returning `True` for valid numbers and `False` for invalid numbers. @@ -152,19 +155,24 @@ def check_validates(cls, value: Optional[str]) -> bool: return is_valid @classmethod - def validate(cls, value: Optional[str], field: fields.ModelField) -> str: # type: ignore + def validate(cls, value: Optional[str | int]) -> str: # type: ignore # pylint: disable=W0221 """Validates the given postcode""" nhs_number = cls.ensure_format(value) if cls.confirm_checksum_validates(nhs_number): - # TODO: Get a better way to get 'loc' here. - cls._warn_for_possible_invalid_number(nhs_number, field.name) + cls._warn_for_possible_invalid_number(nhs_number) return nhs_number raise ValueError("NHS number invalid (incorrect check digit: cannot be a real NHS number)") + @classmethod + def __get_pydantic_core_schema__( + cls, source_type: Any, handler: GetCoreSchemaHandler + ) -> CoreSchema: + return core_schema.no_info_plain_validator_function(cls.validate) + @lru_cache() -@validate_arguments +@validate_call def permissive_nhs_number(warn_on_test_numbers: bool = False): """Defaults to not checking for test numbers""" dict_ = NHSNumber.__dict__.copy() @@ -173,10 +181,10 @@ def permissive_nhs_number(warn_on_test_numbers: bool = False): return type("NHSNumber", (NHSNumber, *NHSNumber.__bases__), dict_) -class Postcode(types.ConstrainedStr): +class Postcode(str): """Postcode constrained string""" - regex: re.Pattern = POSTCODE_REGEX + pattern: re.Pattern = POSTCODE_REGEX strip_whitespace = True apply_normalize = True @@ -198,14 +206,25 @@ def validate(cls, value: str) -> Optional[str]: # type: ignore if not value: return None - if not cls.regex.match(value): + if not cls.pattern.match(value): raise ValueError("Invalid Postcode submitted") return value + @classmethod + def __get_pydantic_core_schema__( + cls, source_type: Any, handler: GetCoreSchemaHandler + ) -> CoreSchema: + return core_schema.chain_schema( + [ + handler(str), + core_schema.no_info_plain_validator_function(cls.validate), + ] + ) + @lru_cache() -@validate_arguments +@validate_call def postcode( # pylint: disable=R0913 strip_whitespace: Optional[bool] = True, @@ -214,8 +233,7 @@ def postcode( strict: Optional[bool] = False, min_length: Optional[int] = None, max_length: Optional[int] = None, - curtail_length: Optional[int] = None, - regex: Optional[str] = POSTCODE_REGEX, # type: ignore + pattern: Optional[str] = POSTCODE_REGEX, # type: ignore apply_normalize: Optional[bool] = True, ) -> type[Postcode]: """Return a formatted date class with a set date format @@ -229,8 +247,7 @@ def postcode( dict_["strict"] = strict dict_["min_length"] = min_length dict_["max_length"] = max_length - dict_["curtail_length"] = curtail_length - dict_["regex"] = regex + dict_["pattern"] = pattern dict_["apply_normalize"] = apply_normalize return type("Postcode", (Postcode, *Postcode.__bases__), dict_) @@ -244,17 +261,17 @@ class OrgID(_SimpleRegexValidator): """ - regex = re.compile(r"^[A-Z0-9]{3,5}$") + pattern = re.compile(r"^[A-Z0-9]{3,5}$") strip_whitespace = False @classmethod - def validate(cls, value: str) -> str: - """Validates the given OrgID""" - if not value: - raise ValueError("org_id not provided") - return super().validate(value) + def __get_pydantic_core_schema__( + cls, source_type: Any, handler: GetCoreSchemaHandler + ) -> CoreSchema: + return core_schema.str_schema(pattern=cls.pattern, strip_whitespace=cls.strip_whitespace) +# TODO - look into replacing datetime types with AwareDatetime and NaiveDatetime from pydantic v2 class ConFormattedDate(dt.date): """A date, provided as a date or a string in a specific format.""" @@ -274,12 +291,9 @@ class ConFormattedDate(dt.date): @classmethod def validate(cls, value: Optional[Union[dt.date, str]]) -> Optional[dt.date]: """Validate a passed datetime or string.""" - if value is None: - return value - if isinstance(value, dt.date): date = value - elif cls.DATE_FORMAT is not None: + elif cls.DATE_FORMAT is not None and value: try: date = dt.datetime.strptime(value, cls.DATE_FORMAT).date() if cls.strict and (date.strftime(cls.DATE_FORMAT) != value): @@ -311,14 +325,20 @@ def validate_range(cls, value) -> Optional[dt.date]: return value @classmethod - def __get_validators__(cls) -> Iterator[classmethod]: - """Gets all validators""" - yield cls.validate # type: ignore - yield cls.validate_range # type: ignore + def __get_pydantic_core_schema__( + cls, source_type: Any, handler: GetCoreSchemaHandler + ) -> CoreSchema: + return core_schema.chain_schema( + [ + handler(str), + core_schema.no_info_plain_validator_function(cls.validate), + core_schema.no_info_plain_validator_function(cls.validate_range), + ] + ) @lru_cache() -@validate_arguments +@validate_call def conformatteddate( date_format: Optional[str] = None, strict: Optional[bool] = False, @@ -381,6 +401,7 @@ def reformat_nhs_string_format(string: str) -> str: ) ) + # TODO - check this hasn't broken as pydantic.parse_datetime retired although this not using pydantic datetime AFAIK pylint: disable=C0301 @classmethod def parse_datetime(cls, string: str) -> dt.datetime: """Attempt to parse a datetime using various formats in sequence.""" @@ -403,6 +424,8 @@ def parse_datetime(cls, string: str) -> dt.datetime: @classmethod def validate(cls, value: Optional[Union[dt.datetime, str]]) -> Optional[dt.datetime]: """Validate a passed datetime or string.""" + # TODO - This check is simply needed because of the test in test_domain_types which is possibly invalid post pylint: disable=C0301 + # Pydantic v2 upgrade now as NoneType will be handled by the handler if value is None: return value @@ -427,9 +450,17 @@ def validate(cls, value: Optional[Union[dt.datetime, str]]) -> Optional[dt.datet return datetime @classmethod - def __get_validators__(cls) -> Iterator[classmethod]: + def __get_pydantic_core_schema__( + cls, source_type: Any, handler: GetCoreSchemaHandler + ) -> CoreSchema: """Gets all validators""" - yield cls.validate # type: ignore + # yield cls.validate # type: ignore + return core_schema.chain_schema( + [ + handler(str), + core_schema.no_info_plain_validator_function(cls.validate), + ] + ) class FormattedTime(dt.time): @@ -496,9 +527,6 @@ def parse_time(cls, string: str) -> dt.time: @classmethod def validate(cls, value: Union[dt.time, dt.datetime, str]) -> dt.time | None: """Validate a passed time, datetime or string.""" - if value is None: - return value - if isinstance(value, dt.time): new_time = value elif isinstance(value, dt.datetime): @@ -524,13 +552,20 @@ def validate(cls, value: Union[dt.time, dt.datetime, str]) -> dt.time | None: return new_time @classmethod - def __get_validators__(cls) -> Iterator[classmethod]: + def __get_pydantic_core_schema__( + cls, source_type: Any, handler: GetCoreSchemaHandler + ) -> CoreSchema: """Gets all validators""" - yield cls.validate # type: ignore + return core_schema.chain_schema( + [ + handler(str), + core_schema.no_info_plain_validator_function(cls.validate), + ] + ) @lru_cache() -@validate_arguments +@validate_call def formatteddatetime( date_format: Optional[str] = None, timezone_treatment: Literal["forbid", "permit", "require"] = "permit", @@ -550,7 +585,7 @@ def formatteddatetime( @lru_cache() -@validate_arguments +@validate_call def formattedtime( time_format: Optional[str] = None, timezone_treatment: Literal["forbid", "permit", "require"] = "permit", @@ -612,13 +647,20 @@ def validate(cls, value: Union[dt.date, str]) -> Optional[dt.date]: return value @classmethod - def __get_validators__(cls) -> Iterator[classmethod]: + def __get_pydantic_core_schema__( + cls, source_type: Any, handler: GetCoreSchemaHandler + ) -> CoreSchema: """Gets all validators""" - yield cls.validate # type: ignore + return core_schema.chain_schema( + [ + handler(dt.date), + core_schema.no_info_plain_validator_function(cls.validate), + ] + ) # @lru_cache() -@validate_arguments +@validate_call def reportingperiod( reporting_period_type: Literal["start", "end"], date_format: Optional[str] = "%Y-%m-%d" ) -> type[ReportingPeriod]: @@ -632,11 +674,12 @@ def reportingperiod( return type("ReportingPeriod", (ReportingPeriod, *ReportingPeriod.__bases__), dict_) +# TODO - refactor this as it won't work in Pyndatic V2 @lru_cache() -@validate_arguments +@validate_call def alphanumeric( - min_digits: types.NonNegativeInt = 1, - max_digits: types.PositiveInt = 1, + min_digits: types.NonNegativeInt = 1, # pylint: disable=E1101 + max_digits: types.PositiveInt = 1, # pylint: disable=E1101 ) -> type[_SimpleRegexValidator]: """Return a regex-validated class which will ensure that passed numbers are alphanumeric. @@ -651,7 +694,7 @@ def alphanumeric( pattern_str = f"{an_group_str}{{{min_digits},{max_digits}}}" dict_ = _SimpleRegexValidator.__dict__.copy() - dict_["regex"] = re.compile(f"^{pattern_str}$") + dict_["pattern"] = re.compile(f"^{pattern_str}$") return type( type_name, @@ -660,11 +703,12 @@ def alphanumeric( ) +# TODO - refactor this as it won't work in Pyndatic V2 @lru_cache() -@validate_arguments +@validate_call def identifier( - min_digits: types.NonNegativeInt = 1, - max_digits: types.PositiveInt = 1, + min_digits: types.NonNegativeInt = 1, # pylint: disable=E1101 + max_digits: types.PositiveInt = 1, # pylint: disable=E1101 ) -> type[_SimpleRegexValidator]: """ Return a regex-validated class which will ensure that @@ -680,7 +724,7 @@ def identifier( pattern_str = rf"{id_group_str}{{{min_digits},{max_digits}}}" dict_ = _SimpleRegexValidator.__dict__.copy() - dict_["regex"] = re.compile(f"^{pattern_str}$") + dict_["pattern"] = re.compile(f"^{pattern_str}$") return type( type_name, diff --git a/src/dve/metadata_parser/function_library.py b/src/dve/metadata_parser/function_library.py index 0e0e5af..87d0dd4 100644 --- a/src/dve/metadata_parser/function_library.py +++ b/src/dve/metadata_parser/function_library.py @@ -31,7 +31,7 @@ def inner(value, *args, **kwargs): # demo function @_nullcheck -@pydantic.validate_arguments +@pydantic.validate_call def normalise(value, capitalize: bool = False): # pragma: no cover """Normalises a string by capitalising it""" if capitalize: @@ -48,7 +48,7 @@ def exclude_word(value, word: str): @_nullcheck -@pydantic.validate_arguments +@pydantic.validate_call def split(value, split_on: str, keep: int = 0): """Splits a string on a given delimiter and keeps only the value at the given index defaults to 0 diff --git a/src/dve/metadata_parser/function_wrapper.py b/src/dve/metadata_parser/function_wrapper.py index eb69546..cea12c0 100644 --- a/src/dve/metadata_parser/function_wrapper.py +++ b/src/dve/metadata_parser/function_wrapper.py @@ -9,13 +9,14 @@ from dve.metadata_parser import exc PydanticCompatible = Callable[ - [Any, dict[str, Any], pydantic.fields.ModelField, pydantic.BaseConfig], Any + [Any, dict[str, Any], pydantic.fields.FieldInfo, pydantic.ConfigDict], # pylint: disable=E1101 + Any, ] """Function Compatable with pydantic Args: value (Any): Value to be validated values (dict[str, Any]): dict of previously validated fields - field (pydantic.fields.ModelField): field object containing field name and type + field (pydantic.fields.FieldInfo): field object containing field name and type config (pydantic.BaseConfig): the config that determines things like aliases """ @@ -24,21 +25,21 @@ def error_handler( error_type: Union[type[Exception], type[Warning]], error_message: str, - field: pydantic.fields.ModelField, + field: pydantic.fields.FieldInfo, # pylint: disable=E1101 ): """Determines whether to raise an error or warning based on error_type Args: error_type (Union[type[Exception], type[Warning]]): type of error to raise error_message (str): message to apply - field (pydantic.fields.ModelField): field that caused the error to be raised + field (pydantic.fields.FieldInfo): field that caused the error to be raised Raises: error_type """ if issubclass(error_type, exc.LocWarning): - warnings.warn(error_type(msg=error_message, loc=field.name)) + warnings.warn(error_type(msg=error_message, loc=field.title)) elif issubclass(error_type, Warning): warnings.warn(error_message, error_type) else: @@ -90,15 +91,15 @@ def wrapper( Returns: Callable: wrapped function with call signature: - (value: Any, values: dict, field: ModelField, config: BaseConfig) -> Any + (value: Any, values: dict, field: FieldInfo, config: BaseConfig) -> Any """ def inner( value: Any, values: dict[str, Any], - field: pydantic.fields.ModelField, # pylint: disable=unused-argument - config: pydantic.BaseConfig, # pylint: disable=unused-argument + field: pydantic.fields.FieldInfo, # pylint: disable=unused-argument,E1101 + config: pydantic.ConfigDict, # pylint: disable=unused-argument ) -> Any: fields = [values.get(name) for name in field_names] result = None @@ -120,7 +121,7 @@ def inner( return wrapper -validator_args = pydantic.validator.__kwdefaults__.copy() +validator_args = pydantic.field_validator.__kwdefaults__.copy() # type: ignore def create_validator( @@ -153,7 +154,6 @@ def create_validator( always: bool = False check_fields: bool = True whole: bool = None - allow_reuse: bool = False function kwargs @@ -176,26 +176,19 @@ def create_validator( **kwargs, )(function) - validator_kwargs.update(allow_reuse=True) - validator = pydantic.validator(field, **validator_kwargs)(wrapped) + validator = pydantic.field_validator(field, **validator_kwargs)(wrapped) return validator -@pydantic.validate_arguments +@pydantic.validate_call def _validator_kwargs( - pre: bool = False, - each_item: bool = False, - always: bool = False, - check_fields: bool = True, - whole: Optional[bool] = None, - allow_reuse: bool = False, + mode: str = "after", + check_fields: bool | None = True, + json_schema_input_type: Any = None, **kwargs, # pylint: disable=unused-argument ): return { - "pre": pre, - "each_item": each_item, - "always": always, + "mode": mode, "check_fields": check_fields, - "whole": whole, - "allow_reuse": allow_reuse, + "json_schema_input_type": json_schema_input_type, } diff --git a/src/dve/metadata_parser/model_generator.py b/src/dve/metadata_parser/model_generator.py index 2706458..892fbe1 100644 --- a/src/dve/metadata_parser/model_generator.py +++ b/src/dve/metadata_parser/model_generator.py @@ -37,7 +37,7 @@ """ -@pyd.validate_arguments +@pyd.validate_call def constr( *, strip_whitespace: bool = False, @@ -45,7 +45,6 @@ def constr( strict: bool = False, min_length: Optional[int] = None, max_length: Optional[int] = None, - curtail_length: Optional[int] = None, regex: Optional[str] = None, ): """Wrapper around constr to enable argument validation""" @@ -55,16 +54,15 @@ def constr( strict=strict, min_length=min_length, # type: ignore max_length=max_length, # type: ignore - curtail_length=curtail_length, # type: ignore - regex=regex, # type: ignore + pattern=regex, # type: ignore ) STR_TO_PY_MAPPING: Mapping[str, FieldTypeOption] = { "constr": constr, - "conint": pyd.validate_arguments(pyd.conint), - "condate": pyd.validate_arguments(pyd.condate), - "condecimal": pyd.validate_arguments(pyd.condecimal), + "conint": pyd.validate_call(pyd.conint), + "condate": pyd.validate_call(pyd.condate), + "condecimal": pyd.validate_call(pyd.condecimal), "postcode": domain_types.postcode, "nhsnumber": domain_types.NHSNumber, "permissivenhsno": domain_types.permissive_nhs_number(), @@ -89,7 +87,7 @@ def __init__(self, contract_contents: dict[str, Any], type_map: Optional[dict] = @abstractmethod def generate_models( self, additional_validators: Optional[dict] = None - ) -> dict[str, pyd.main.ModelMetaclass]: + ) -> dict[str, pyd.BaseModel]: """Generates models from the instance schema. Args: @@ -112,7 +110,7 @@ def __init__(self, contract_contents: dict[str, Any], type_map: Optional[dict] = def generate_models( self, additional_validators: Optional[dict] = None - ) -> dict[str, pyd.main.ModelMetaclass]: + ) -> dict[str, pyd.BaseModel]: """Generates pydantic models from a loaded json file""" if additional_validators: warnings.warn("Ignoring additional validator functions") diff --git a/src/dve/metadata_parser/models.py b/src/dve/metadata_parser/models.py index 73e6b5c..fd8ed6f 100644 --- a/src/dve/metadata_parser/models.py +++ b/src/dve/metadata_parser/models.py @@ -5,11 +5,11 @@ import warnings from collections import Counter from collections.abc import Mapping, MutableMapping -from typing import Any, Optional, Union +from typing import Annotated, Any, Optional, Union import pydantic as pyd -from pydantic import BaseModel, Field, root_validator, validator -from typing_extensions import Literal +from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, field_validator, model_validator +from typing_extensions import Literal, get_origin from dve.metadata_parser import exc, function_library from dve.metadata_parser.function_wrapper import create_validator @@ -27,7 +27,7 @@ """An alias for a field.""" EntityName = str """The name of an entity.""" -PydanticType = Union[type, pyd.main.ModelMetaclass] +PydanticType = Union[type, pyd.BaseModel] """A pydantic-appropriate type.""" ValidatorName = str """The name of a validator.""" @@ -63,24 +63,25 @@ class ValidationFunctionSpecification(BaseModel): # type: ignore kwargs_: dict[str, Any] = Field(default_factory=dict, alias="kwargs") """Keyword arguments for the validation function.""" - @validator("name", allow_reuse=True) - def validate_name(cls, value: str) -> str: + @field_validator("name") + def validate_name(cls, value: str, info: ValidationInfo) -> str: # pylint: disable=W0613 """Ensure that the name exists in the function library.""" if not hasattr(function_library, value): raise ValueError(f"Function {value!r} not available in function library") return value - @validator("error_message", allow_reuse=True) - def validate_error_message(cls, value: str, values: dict[str, Any]) -> str: + @field_validator("error_message") + def validate_error_message(cls, value: str, info: ValidationInfo) -> str: """set a default error message if one is not available.""" if value: return value - name: str = values["name"] + name: str = info.data["name"] return f"{name} failed" def get_field_validator(self, field_name: str, **extra_kwargs: Any) -> classmethod: """Get a validator a given field.""" func = getattr(function_library, self.name) + _kwargs = self.kwargs_ | extra_kwargs return create_validator( func, field_name, @@ -88,8 +89,7 @@ def get_field_validator(self, field_name: str, **extra_kwargs: Any) -> classmeth self.error_message, return_result=True, fields=self.fields, - **self.kwargs_, - **extra_kwargs, + **_kwargs, ) @@ -128,12 +128,15 @@ class FieldSpecification(BaseModel): functions: list[ValidationFunctionSpecification] = Field(default_factory=list) """Validation functions to be applied to the type.""" - @root_validator(allow_reuse=True) - def ensure_one_type_spec_method(cls, values: dict[str, Any]) -> dict[str, Any]: + model_config = {"validate_assignment": True} + + @model_validator(mode="after") # type: ignore + @classmethod + def ensure_one_type_spec_method(cls, field_spec) -> dict[str, Any]: """Ensure that exactly one of 'type', 'model' and 'callable' was specified.""" - has_type = bool(values.get("type_")) - has_model = bool(values.get("model")) - has_callable = bool(values.get("callable")) + has_type = bool(field_spec.type_) + has_model = bool(field_spec.model) + has_callable = bool(field_spec.callable) n_specified = sum((has_type, has_model, has_callable)) failure_messages = [ @@ -156,21 +159,21 @@ def ensure_one_type_spec_method(cls, values: dict[str, Any]) -> dict[str, Any]: failure_messages.append(f"Got {supplied}") raise ValueError(" ".join(failure_messages)) - if not has_callable and values.get("constraints"): + if not has_callable and field_spec.constraints: warnings.warn( "'constraints' only used when field specification uses 'callable'", category=UnusedConstraints, ) - return values + return field_spec - @validator("default", allow_reuse=True) - def validate_default(cls, value: Any, values: dict[str, Any]) -> Any: + @field_validator("default") + def validate_default(cls, value: Any, info: ValidationInfo) -> Any: """Validate that 'default' is aligned with 'is_array'.""" if value is None: return value - is_array = bool(values.get("is_array")) + is_array = bool(info.data.get("is_array")) if is_array: if not isinstance(value, list): warnings.warn( @@ -212,7 +215,7 @@ def get_type_and_validators( self, field_name: str, *type_mappings: Mapping[TypeName, FieldTypeOption], - schemas: Optional[dict[EntityName, pyd.main.ModelMetaclass]] = None, + schemas: Optional[dict[EntityName, pyd.BaseModel]] = None, is_mandatory: bool = False, ) -> tuple[PydanticType, Default, Validators]: """Get the type, default value, and validators for the specification.""" @@ -223,12 +226,14 @@ def get_type_and_validators( possible_python_type = chain_get(self.type_, *type_mappings, pyd, dt, __builtins__) if isinstance(possible_python_type, type): python_type = possible_python_type + elif get_origin(possible_python_type) is Annotated: + python_type = possible_python_type # type: ignore elif hasattr(possible_python_type, "get_type_and_validators"): possible_python_type: "FieldSpecification" # type: ignore nested_vals = possible_python_type.get_type_and_validators( # type: ignore field_name, *type_mappings, schemas=schemas, is_mandatory=False ) - python_type, nested_default, nested_validators = nested_vals + python_type, nested_default, nested_validators = nested_vals # type: ignore if nested_validators and self.is_array: # Need to work out how to hook into the validators and update @@ -248,7 +253,7 @@ def get_type_and_validators( if not schemas: raise ValueError("Type should be model, but `schemas` not passed") try: - python_type = schemas[self.model] + python_type = schemas[self.model] # type: ignore except KeyError as err: raise ValueError( f"Type should be model {self.model!r} but this is not in `schemas`" @@ -257,20 +262,25 @@ def get_type_and_validators( python_type_callable = chain_get(self.callable, *type_mappings, pyd, dt, __builtins__) if not callable(python_type_callable): raise ValueError("Fetched callable is not callable") - python_type = python_type_callable(**self.constraints) + python_type = python_type_callable(**self.constraints) # pylint: disable=E1134 else: raise ValueError("No field type set") default = default or (... if is_mandatory else None) + if self.is_array: python_type = list[python_type] # type: ignore + + if not is_mandatory: + python_type = Optional[python_type] # type: ignore + return python_type, default, validators class EntitySpecification(BaseModel): """Configuration options for an entity.""" - fields: dict[FieldName, FieldSpecification] + fields: Annotated[dict[FieldName, FieldSpecification], Field(validate_default=True)] """ A mapping of field names to their Python types. These will either be strings representing Python types (if there are no argumements to the type), @@ -282,9 +292,11 @@ class EntitySpecification(BaseModel): mandatory_fields: list[FieldName] = Field(default_factory=list) """An array of field names which should be considered mandatory.""" - @validator("fields", pre=True, allow_reuse=True) + @field_validator("fields", mode="before") def validate_fields( - cls, value: dict[FieldName, Union[TypeName, FieldSpecification]] + cls, + value: dict[FieldName, Union[TypeName, FieldSpecification]], + info: ValidationInfo, # pylint: disable=W0613 ) -> dict[FieldName, FieldSpecification]: """Convert bare string fields to field specifications.""" for key in value: @@ -294,9 +306,9 @@ def validate_fields( return value # type: ignore - @validator("aliases", allow_reuse=True) + @field_validator("aliases") def validate_aliases( - cls, value: dict[FieldName, FieldAlias], values: dict[str, Any] + cls, value: dict[FieldName, FieldAlias], info: ValidationInfo ) -> dict[FieldName, FieldAlias]: """Ensure that 'aliases' is aligned with 'fields'.""" # Check that aliases are not given more than once @@ -316,7 +328,7 @@ def validate_aliases( + f"more than once: {multiple_occurrences}" ) # And warn when unnecessary aliases were given. - field_names: set[FieldName] = set(values["fields"].keys()) + field_names: set[FieldName] = set(info.data["fields"].keys()) missing_fields = set(value.keys()) - field_names if missing_fields: warnings.warn( @@ -327,15 +339,15 @@ def validate_aliases( return value - @validator("mandatory_fields", allow_reuse=True) + @field_validator("mandatory_fields") def validate_mandatory_fields( - cls, value: list[FieldName], values: dict[str, Any] + cls, value: list[FieldName], info: ValidationInfo ) -> list[FieldName]: """Ensure that 'mandatory_fields' is aligned with 'fields'.""" if not value: return value - field_names: set[FieldName] = set(values["fields"].keys()) + field_names: set[FieldName] = set(info.data["fields"].keys()) missing_fields = set(value) - field_names if missing_fields: raise ValueError( @@ -349,8 +361,8 @@ def as_model( self, model_name: str, *type_mappings: Mapping[TypeName, FieldTypeOption], - schemas: Optional[dict[EntityName, pyd.main.ModelMetaclass]] = None, - ) -> pyd.main.ModelMetaclass: + schemas: Optional[dict[EntityName, pyd.BaseModel]] = None, + ) -> pyd.BaseModel: """Get the pydantic model from an entity definition.""" validators = {} pyd_fields = {} @@ -360,23 +372,17 @@ def as_model( field_name, *type_mappings, schemas=schemas, - is_mandatory=field_name in self.mandatory_fields, + is_mandatory=field_name in self.mandatory_fields, # pylint: disable=E1135 ) pyd_fields[field_name] = (python_type, default) validators.update(field_validators) - class Config(pyd.BaseConfig): - """Model configuration.""" - - fields = self.aliases # type: ignore - anystr_strip_whitespace = True - allow_population_by_field_name = True - extra = pyd.Extra.ignore - return pyd.create_model( # type: ignore model_name, **pyd_fields, - __config__=Config, # type: ignore + __config__=ConfigDict( + str_strip_whitespace=True, populate_by_name=True, extra="ignore" + ), # type: ignore __validators__=validators, ) @@ -395,10 +401,10 @@ class DatasetSpecification(BaseModel): def load_models( self, *type_mappings: Mapping[TypeName, FieldTypeOption], - ) -> dict[EntityName, pyd.main.ModelMetaclass]: + ) -> dict[EntityName, pyd.BaseModel]: """Load the models from the dataset definition.""" - loaded_schemas: dict[EntityName, pyd.main.ModelMetaclass] = {} - for model_name, specification in self.schemas.items(): + loaded_schemas: dict[EntityName, pyd.BaseModel] = {} + for model_name, specification in self.schemas.items(): # pylint: disable=E1101 loaded_schemas[model_name] = specification.as_model( model_name, self.types, *type_mappings, schemas=loaded_schemas ) diff --git a/src/dve/parser/file_handling/helpers.py b/src/dve/parser/file_handling/helpers.py index 9079e53..7ff9046 100644 --- a/src/dve/parser/file_handling/helpers.py +++ b/src/dve/parser/file_handling/helpers.py @@ -12,13 +12,13 @@ class NonClosingTextIOWrapper(TextIOWrapper): """ - def __exit__(self, *_): # pragma: no cover + def __exit__(self, *_): # pragma: no cover # pylint: disable=R1711 """Exits the context and detaches""" try: self.detach() except ValueError: # Assume all ValuesErrors are safe to absorb. - return + return # pylint: disable=R1711 def parse_uri(uri: URI) -> tuple[Scheme, Hostname, URIPath]: diff --git a/src/dve/parser/file_handling/implementations/file.py b/src/dve/parser/file_handling/implementations/file.py index 76d8b58..9fb29db 100644 --- a/src/dve/parser/file_handling/implementations/file.py +++ b/src/dve/parser/file_handling/implementations/file.py @@ -2,7 +2,7 @@ import platform import shutil -from collections.abc import Callable, Iterator +from collections.abc import Iterator from pathlib import Path from typing import IO, Any, NoReturn, Optional from urllib.parse import unquote @@ -12,7 +12,7 @@ from dve.parser.exceptions import FileAccessError from dve.parser.file_handling.helpers import parse_uri from dve.parser.file_handling.implementations.base import BaseFilesystemImplementation -from dve.parser.type_hints import URI, NodeType, PathStr, Scheme +from dve.parser.type_hints import URI, NodeType, Scheme FILE_URI_SCHEMES: set[Scheme] = {"file"} """A set of all allowed file URI schemes.""" @@ -179,9 +179,9 @@ def _transfer_resource( parent.mkdir(exist_ok=True) if action == "copy": - func: Callable[[PathStr, PathStr], None] = shutil.copy + func = shutil.copy # type: ignore elif action == "move": - func = shutil.move + func = shutil.move # type: ignore else: # pragma: no cover raise ValueError(f"Unsupported action {action!r}, expected one of: 'copy', 'move'") try: diff --git a/src/dve/pipeline/pipeline.py b/src/dve/pipeline/pipeline.py index fae60b6..8c32021 100644 --- a/src/dve/pipeline/pipeline.py +++ b/src/dve/pipeline/pipeline.py @@ -13,7 +13,7 @@ from uuid import uuid4 import polars as pl -from pydantic import validate_arguments +from pydantic import validate_call import dve.reporting.excel_report as er from dve.common.error_utils import ( @@ -108,7 +108,7 @@ def data_contract(self) -> BaseDataContract: @property def step_implementations(self) -> Optional[BaseStepImplementations[EntityType]]: """The step implementations to apply the business rules to a given dataset""" - return self._step_implementations + return self._step_implementations # type: ignore @property def backend_reader_kwargs(self) -> dict[str, Any] | None: @@ -139,7 +139,7 @@ def get_submission_status( return SubmissionStatus() return submission_status - @validate_arguments + @validate_call def _move_submission_to_working_location( self, submission_id: str, @@ -824,7 +824,7 @@ def error_report( summary_dict = { key.replace("_", " ").title(): value - for key, value in submission_info.dict().items() + for key, value in submission_info.model_dump().items() if value is not None and not key.endswith("_updated") } summary_items = er.SummaryItems( diff --git a/src/dve/pipeline/utils.py b/src/dve/pipeline/utils.py index eebaa90..4fe114e 100644 --- a/src/dve/pipeline/utils.py +++ b/src/dve/pipeline/utils.py @@ -5,7 +5,7 @@ from threading import Lock from typing import Any, Optional -from pydantic.main import ModelMetaclass +from pydantic import BaseModel from pyspark.sql import SparkSession import dve.core_engine.backends.implementations.duckdb # pylint: disable=unused-import @@ -18,7 +18,7 @@ from dve.metadata_parser.model_generator import JSONtoPyd Dataset = dict[SchemaName, _ModelConfig] -_configs: dict[str, tuple[dict[str, ModelMetaclass], V1EngineConfig, Dataset]] = {} +_configs: dict[str, tuple[dict[str, BaseModel], V1EngineConfig, Dataset]] = {} locks = Lock() logger = get_logger(__name__) @@ -27,7 +27,7 @@ def load_config( dataset_id: str, file_uri: URI, -) -> tuple[dict[SchemaName, ModelMetaclass], V1EngineConfig, dict[SchemaName, _ModelConfig]]: +) -> tuple[dict[SchemaName, BaseModel], V1EngineConfig, dict[SchemaName, _ModelConfig]]: """Loads the configuration for a given dataset""" if dataset_id in _configs: return _configs[dataset_id] diff --git a/tests/features/patches.py b/tests/features/patches.py index 6db6a8c..c31aef4 100644 --- a/tests/features/patches.py +++ b/tests/features/patches.py @@ -89,7 +89,7 @@ def get_spark_session() -> SparkSession: os.environ["PYSPARK_SUBMIT_ARGS"] = " ".join( [ "--packages", - "com.databricks:spark-xml_2.12:0.16.0,io.delta:delta-core_2.12:2.4.0", + "com.databricks:spark-xml_2.12:0.16.0,io.delta:delta-spark_2.12:3.2.0", "pyspark-shell", ] ) diff --git a/tests/features/steps/steps_post_pipeline.py b/tests/features/steps/steps_post_pipeline.py index be679ba..906445e 100644 --- a/tests/features/steps/steps_post_pipeline.py +++ b/tests/features/steps/steps_post_pipeline.py @@ -108,7 +108,7 @@ def check_stats_record(context): record: Dict[str, str] = row.as_dict() expected[record["parameter"]] = int(record["value"]) stats = ( - get_pipeline(context)._audit_tables.get_submission_statistics(sub_info.submission_id).dict() + get_pipeline(context)._audit_tables.get_submission_statistics(sub_info.submission_id).model_dump() ) assert all([val == stats.get(fld) for fld, val in expected.items()]) diff --git a/tests/fixtures.py b/tests/fixtures.py index 457cf42..fa79eec 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -12,9 +12,6 @@ from moto import mock_s3 # type: ignore from pyspark.sql import SparkSession -from dve.core_engine.backends.implementations.duckdb.duckdb_helpers import ( - PYTHON_TYPE_TO_DUCKDB_TYPE, -) from dve.parser.file_handling.implementations import DBFSFilesystemImplementation from dve.parser.file_handling.service import ( add_implementation, diff --git a/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_audit_ddb.py b/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_audit_ddb.py index 5daa471..3898ad5 100644 --- a/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_audit_ddb.py +++ b/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_audit_ddb.py @@ -344,11 +344,11 @@ def test_get_error_report_submissions(ddb_audit_manager_threaded: DDBAuditingMan expected = [ SubmissionInfo( submission_id=sub_1.submission_id, - **{fld: val for fld, val in sub_1.dict().items() if fld != "submission_id"}, + **{fld: val for fld, val in sub_1.model_dump().items() if fld != "submission_id"}, ), SubmissionInfo( submission_id=sub_3.submission_id, - **{fld: val for fld, val in sub_3.dict().items() if fld != "submission_id"}, + **{fld: val for fld, val in sub_3.model_dump().items() if fld != "submission_id"}, ), ] assert len(processed) == 2 diff --git a/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_data_contract.py b/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_data_contract.py index 74edce9..2019a66 100644 --- a/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_data_contract.py +++ b/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_data_contract.py @@ -96,8 +96,8 @@ def test_duckdb_data_contract_csv(temp_csv_file): entities, feedback_errors_uri, stage_successful = data_contract.apply_data_contract(get_parent(uri.as_posix()), entities, entity_locations, dc_meta) rel: DuckDBPyRelation = entities.get("test_ds") expected_schema = { - fld.name: str(get_duckdb_type_from_annotation(fld.annotation)) - for fld in mdl.__fields__.values() + name: str(get_duckdb_type_from_annotation(fld.annotation)) + for name, fld in mdl.model_fields.items() } expected_schema[RECORD_INDEX_COLUMN_NAME] = get_duckdb_type_from_annotation(int) assert dict(zip(rel.columns, rel.dtypes)) == expected_schema @@ -196,13 +196,13 @@ def test_duckdb_data_contract_xml(temp_xml_file): entities, feedback_errors_uri, stage_successful = data_contract.apply_data_contract(get_parent(uri.as_posix()), entities, entity_locations, dc_meta) header_rel: DuckDBPyRelation = entities.get("test_header") header_expected_schema: Dict[str, DuckDBPyType] = { - fld.name: get_duckdb_type_from_annotation(fld.type_) - for fld in header_model.__fields__.values() + name: get_duckdb_type_from_annotation(fld.annotation) + for name, fld in header_model.model_fields.items() } header_expected_schema[RECORD_INDEX_COLUMN_NAME] = get_duckdb_type_from_annotation(int) class_data_expected_schema: Dict[str, DuckDBPyType] = { - fld.name: get_duckdb_type_from_annotation(fld.type_) - for fld in class_model.__fields__.values() + name: get_duckdb_type_from_annotation(fld.annotation) + for name, fld in class_model.model_fields.items() } class_data_expected_schema[RECORD_INDEX_COLUMN_NAME] = get_duckdb_type_from_annotation(int) class_data_rel: DuckDBPyRelation = entities.get("test_class_info") diff --git a/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_duckdb_helpers.py b/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_duckdb_helpers.py index 19e96e2..87e668f 100644 --- a/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_duckdb_helpers.py +++ b/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_duckdb_helpers.py @@ -168,9 +168,9 @@ def test_get_duckdb_cast_statement_from_annotation(field_name, field_type, cast_ def test_use_cast_statements(casting_test_table): _, conn = casting_test_table test_rel = conn.sql("SELECT * from test_casting") - casting_statements = [ f"{get_duckdb_cast_statement_from_annotation(fld.name, fld.annotation)} as {fld.name}" for fld in CastingRecord.__fields__.values()] + casting_statements = [ f"{get_duckdb_cast_statement_from_annotation(name, fld.annotation)} as {name}" for name, fld in CastingRecord.model_fields.items()] test_rel = test_rel.project(",".join(casting_statements)) - assert dict(zip(test_rel.columns, test_rel.dtypes)) == {fld.name: get_duckdb_type_from_annotation(fld.annotation) for fld in CastingRecord.__fields__.values()} + assert dict(zip(test_rel.columns, test_rel.dtypes)) == {name: get_duckdb_type_from_annotation(fld.annotation) for name, fld in CastingRecord.model_fields.items()} dodgy_date_rec = test_rel.pl()[1].to_dicts()[0] assert (not dodgy_date_rec.get("date_test") and not dodgy_date_rec.get("basic_model",{}).get("date_field") diff --git a/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_rules.py b/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_rules.py index 5d3d3a8..35007a9 100644 --- a/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_rules.py +++ b/tests/test_core_engine/test_backends/test_implementations/test_duckdb/test_rules.py @@ -69,7 +69,7 @@ def test_column_addition(planets_rel: DuckDBPyRelation): rule = ColumnAddition( entity_name="planets", column_name="literal_one", - expression=1, + expression="1", new_entity_name="added", ) _, success = DUCKDB_STEP_BACKEND.evaluate(entities, config=rule) @@ -87,7 +87,7 @@ def test_column_addition_missing_entity(): rule = ColumnAddition( entity_name="planets", column_name="literal_one", - expression=1, + expression="1", new_entity_name="added", ) messages, success = DUCKDB_STEP_BACKEND.evaluate(entities, config=rule) diff --git a/tests/test_core_engine/test_backends/test_implementations/test_spark/test_audit_spark.py b/tests/test_core_engine/test_backends/test_implementations/test_spark/test_audit_spark.py index 4b328df..7da7f48 100644 --- a/tests/test_core_engine/test_backends/test_implementations/test_spark/test_audit_spark.py +++ b/tests/test_core_engine/test_backends/test_implementations/test_spark/test_audit_spark.py @@ -359,11 +359,11 @@ def test_get_error_report_submissions(spark_audit_manager: SparkAuditingManager) expected = [ SubmissionInfo( submission_id=sub_1.submission_id, - **{fld: val for fld, val in sub_1.dict().items() if fld != "submission_id"}, + **{fld: val for fld, val in sub_1.model_dump().items() if fld != "submission_id"}, ), SubmissionInfo( submission_id=sub_3.submission_id, - **{fld: val for fld, val in sub_3.dict().items() if fld != "submission_id"}, + **{fld: val for fld, val in sub_3.model_dump().items() if fld != "submission_id"}, ), ] assert len(processed) == 2 diff --git a/tests/test_core_engine/test_backends/test_implementations/test_spark/test_rules.py b/tests/test_core_engine/test_backends/test_implementations/test_spark/test_rules.py index e0cbf90..673e611 100644 --- a/tests/test_core_engine/test_backends/test_implementations/test_spark/test_rules.py +++ b/tests/test_core_engine/test_backends/test_implementations/test_spark/test_rules.py @@ -59,7 +59,7 @@ def test_column_addition(planets_df: DataFrame): rule = ColumnAddition( entity_name="planets", column_name="literal_one", - expression=1, + expression="1", new_entity_name="added", ) _, success = SPARK_STEP_BACKEND.evaluate(entities, config=rule) @@ -77,7 +77,7 @@ def test_column_addition_missing_entity(): rule = ColumnAddition( entity_name="planets", column_name="literal_one", - expression=1, + expression="1", new_entity_name="added", ) messages, success = SPARK_STEP_BACKEND.evaluate(entities, config=rule) diff --git a/tests/test_core_engine/test_backends/test_implementations/test_spark/test_spark_helpers.py b/tests/test_core_engine/test_backends/test_implementations/test_spark/test_spark_helpers.py index 7502673..2a336cb 100644 --- a/tests/test_core_engine/test_backends/test_implementations/test_spark/test_spark_helpers.py +++ b/tests/test_core_engine/test_backends/test_implementations/test_spark/test_spark_helpers.py @@ -256,9 +256,9 @@ def test_get_spark_cast_statement_from_annotation(field_name, field_type, expres def test_use_cast_statements(spark, casting_dataframe): - casting_statements = [ get_spark_cast_statement_from_annotation(fld.name, fld.annotation).alias(fld.name) for fld in CastingRecord.__fields__.values()] + casting_statements = [ get_spark_cast_statement_from_annotation(name, fld.annotation).alias(name) for name, fld in CastingRecord.model_fields.items()] cast_df = casting_dataframe.select(*casting_statements) - assert {fld.name: fld.dataType for fld in cast_df.schema} == {fld.name: get_type_from_annotation(fld.annotation) for fld in CastingRecord.__fields__.values()} + assert {fld.name: fld.dataType for fld in cast_df.schema} == {name: get_type_from_annotation(fld.annotation) for name, fld in CastingRecord.model_fields.items()} dodgy_date_rec = [rw.asDict(True) for rw in cast_df.collect()][1] assert (not dodgy_date_rec.get("date_test") and not dodgy_date_rec.get("basic_model",{}).get("date_field") diff --git a/tests/test_core_engine/test_backends/test_readers/test_csv.py b/tests/test_core_engine/test_backends/test_readers/test_csv.py index 0737ad2..c7b2d6a 100644 --- a/tests/test_core_engine/test_backends/test_readers/test_csv.py +++ b/tests/test_core_engine/test_backends/test_readers/test_csv.py @@ -140,7 +140,7 @@ def test_csv_file_get_subset( parsed = {row["planet"]: row for row in results} # Keep only keys in the subset from the source - subset_keys = set(PlanetsSubset.__fields__.keys()) + subset_keys = set(PlanetsSubset.model_fields.keys()) for data in planet_data.values(): to_pop = set(data.keys()) - subset_keys - {RECORD_INDEX_COLUMN_NAME} for key in to_pop: @@ -162,7 +162,7 @@ def test_csv_file_get_subset_add_missing( parsed = {row["planet"]: row for row in results} # Keep only keys in the subset from the source - subset_keys = set(PlanetsSubset.__fields__.keys()) + subset_keys = set(PlanetsSubset.model_fields.keys()) for data in planet_data.values(): to_pop = set(data.keys()) - subset_keys - {RECORD_INDEX_COLUMN_NAME} for key in to_pop: diff --git a/tests/test_core_engine/test_backends/test_readers/test_ddb_csv.py b/tests/test_core_engine/test_backends/test_readers/test_ddb_csv.py index 78974eb..96bc5f7 100644 --- a/tests/test_core_engine/test_backends/test_readers/test_ddb_csv.py +++ b/tests/test_core_engine/test_backends/test_readers/test_ddb_csv.py @@ -88,8 +88,8 @@ def test_ddb_csv_reader_cast(temp_csv_file): reader = DuckDBCSVReader(header=True, delim=",", connection=duckdb.connect()) rel: DuckDBPyRelation = reader.read_to_entity_type(DuckDBPyRelation, str(uri), "test", mdl) expected_dtypes = {**{ - fld.name: str(get_duckdb_type_from_annotation(fld.annotation)) - for fld in mdl.__fields__.values() + name: str(get_duckdb_type_from_annotation(fld.annotation)) + for name, fld in mdl.model_fields.items() }, RECORD_INDEX_COLUMN_NAME: get_duckdb_type_from_annotation(int)} expected_data = [(*rw, idx) for idx, rw in enumerate(data, start=1)] assert rel.columns == header.split(",") + [RECORD_INDEX_COLUMN_NAME] diff --git a/tests/test_core_engine/test_backends/test_readers/test_ddb_json.py b/tests/test_core_engine/test_backends/test_readers/test_ddb_json.py index 87a90e8..ae8e4ad 100644 --- a/tests/test_core_engine/test_backends/test_readers/test_ddb_json.py +++ b/tests/test_core_engine/test_backends/test_readers/test_ddb_json.py @@ -56,7 +56,7 @@ class SimpleModel(BaseModel): def test_ddb_json_reader_all_str(temp_json_file): uri, data, mdl = temp_json_file - expected_fields = [fld for fld in mdl.__fields__] + expected_fields = [fld for fld in mdl.model_fields] reader = DuckDBJSONReader() rel: DuckDBPyRelation = reader.read_to_entity_type( DuckDBPyRelation, uri.as_posix(), "test", stringify_model(mdl) @@ -68,14 +68,14 @@ def test_ddb_json_reader_all_str(temp_json_file): def test_ddb_json_reader_cast(temp_json_file): uri, data, mdl = temp_json_file - expected_fields = [fld for fld in mdl.__fields__] + expected_fields = [fld for fld in mdl.model_fields] reader = DuckDBJSONReader() rel: DuckDBPyRelation = reader.read_to_entity_type(DuckDBPyRelation, uri.as_posix(), "test", mdl) assert rel.columns == expected_fields + [RECORD_INDEX_COLUMN_NAME] assert dict(zip(rel.columns, rel.dtypes)) == {**{ - fld.name: str(get_duckdb_type_from_annotation(fld.annotation)) - for fld in mdl.__fields__.values() + name: str(get_duckdb_type_from_annotation(fld.annotation)) + for name, fld in mdl.model_fields.items() }, RECORD_INDEX_COLUMN_NAME: "BIGINT"} assert rel.fetchall() == [(*rw.values(), idx) for idx, rw in enumerate(data, start = 1)] diff --git a/tests/test_core_engine/test_backends/test_readers/test_spark_json.py b/tests/test_core_engine/test_backends/test_readers/test_spark_json.py index 24674ca..46d4729 100644 --- a/tests/test_core_engine/test_backends/test_readers/test_spark_json.py +++ b/tests/test_core_engine/test_backends/test_readers/test_spark_json.py @@ -55,7 +55,7 @@ class SimpleModel(BaseModel): def test_spark_json_reader_all_str(temp_json_file): uri, data, mdl = temp_json_file - expected_fields = [fld for fld in mdl.__fields__] + [RECORD_INDEX_COLUMN_NAME] + expected_fields = [fld for fld in mdl.model_fields] + [RECORD_INDEX_COLUMN_NAME] reader = SparkJSONReader() df: DataFrame = reader.read_to_entity_type( DataFrame, uri.as_posix(), "test", stringify_model(mdl) @@ -66,13 +66,13 @@ def test_spark_json_reader_all_str(temp_json_file): def test_spark_json_reader_cast(temp_json_file): uri, data, mdl = temp_json_file - expected_fields = [fld for fld in mdl.__fields__] + [RECORD_INDEX_COLUMN_NAME] + expected_fields = [fld for fld in mdl.model_fields] + [RECORD_INDEX_COLUMN_NAME] reader = SparkJSONReader() df: DataFrame = reader.read_to_entity_type(DataFrame, uri.as_posix(), "test", mdl) assert df.columns == expected_fields - assert df.schema == StructType([StructField(fld.name, get_type_from_annotation(fld.annotation)) - for fld in mdl.__fields__.values()] + [StructField(RECORD_INDEX_COLUMN_NAME, get_type_from_annotation(int))]) + assert df.schema == StructType([StructField(name, get_type_from_annotation(fld.annotation)) + for name, fld in mdl.model_fields.items()] + [StructField(RECORD_INDEX_COLUMN_NAME, get_type_from_annotation(int))]) assert [rw.asDict() for rw in df.collect()] == [{**rw, RECORD_INDEX_COLUMN_NAME: idx} for idx, rw in enumerate(data, start=1)] diff --git a/tests/test_core_engine/test_backends/test_utilities.py b/tests/test_core_engine/test_backends/test_utilities.py new file mode 100644 index 0000000..839d9b7 --- /dev/null +++ b/tests/test_core_engine/test_backends/test_utilities.py @@ -0,0 +1,55 @@ +from datetime import date +from typing import Any + +import pytest +from pydantic import BaseModel + +from dve.core_engine.backends.utilities import is_field_complex + + +class AnotherTestModel(BaseModel): + test_field: str + + +class TestModel(BaseModel): + simple_str: str + simple_int: int + simple_bool: bool + simple_list: list[str] + simple_dict: dict[str, Any] + simple_set: set[str] + simple_tuple: tuple[int, str] + another_model: AnotherTestModel + date_example: date + + +TEST_MODEL = TestModel( + simple_str="abc", + simple_int=123, + simple_bool=True, + simple_list=["apple", "banana", "orange",], + simple_dict={"key1": "abc"}, + simple_set={"a", "b", "c"}, + simple_tuple=(1, "wow"), + another_model=AnotherTestModel(test_field="lemon"), + date_example=date(2026,1,1) +) + + +@pytest.mark.parametrize( + "field_name, expected", + [ + ("simple_str", False), + ("simple_int", False), + ("simple_bool", False), + ("simple_list", True), + ("simple_dict", True), + ("simple_set", True), + ("simple_tuple", True), + ("another_model", True), + ] +) +def test_is_field_complex(field_name: str, expected: bool): + _field = TEST_MODEL.model_fields[field_name] + _res = is_field_complex(_field) + assert _res == expected, f"Expected {expected}. Got {_res}." diff --git a/tests/test_core_engine/test_message.py b/tests/test_core_engine/test_message.py index ccb6736..9a4f8ac 100644 --- a/tests/test_core_engine/test_message.py +++ b/tests/test_core_engine/test_message.py @@ -4,8 +4,9 @@ import json from string import ascii_letters from typing import Dict, List, Optional +from typing_extensions import Annotated -from pydantic import BaseModel, ValidationError +from pydantic import BaseModel, Field, ValidationError import pytest from dve.core_engine.message import DEFAULT_ERROR_DETAIL, DataContractErrorDetail, FeedbackMessage @@ -140,7 +141,7 @@ def test_from_pydantic_error(): class TestModel(BaseModel): idx: int - other_field: str + other_field: Annotated[str, Field(coerce_numbers_to_str=True)] _bad_value_data = {"idx": "ABC", "other_field": 123} _blank_value_data = {"other_field": "hi"} @@ -178,9 +179,9 @@ def test_from_pydantic_error_custom_error_details(): class TestModel(BaseModel): idx: int str_field: str - date_field: Optional[date] - unimportant_field: Optional[int] - + date_field: Annotated[date, Field(default=None)] + unimportant_field: Annotated[int, Field(default=None)] + custom_error_details: str = """ {"idx": {"Blank": {"error_code": "IDBLANKERRCODE", "error_message": "idx is a mandatory field"}, @@ -239,7 +240,7 @@ def test_from_pydantic_error_custom_codes_nested(): class LowestModel(BaseModel): nested_field_3: str - test_date: Optional[date] + test_date: Annotated[date, Field(default=None)] class SubTestModel(BaseModel): nested_field_1: int nested_field_2: List[LowestModel] diff --git a/tests/test_core_engine/test_models.py b/tests/test_core_engine/test_models.py index e187de1..d1a55c4 100644 --- a/tests/test_core_engine/test_models.py +++ b/tests/test_core_engine/test_models.py @@ -1,10 +1,11 @@ """Unit tests for core engine models.""" +from datetime import date, datetime from typing import Any, Dict, Tuple from uuid import uuid4 import pytest -from dve.core_engine.models import SubmissionInfo +from dve.core_engine.models import AuditRecord, SubmissionInfo CONSTANT_SUBMISSION_ID = uuid4().hex @@ -116,7 +117,7 @@ def test_submission_info( # pylint: disable=missing-function-docstring actual = SubmissionInfo(**testcase["submitted"]) expected = testcase["expected"] - assert {k: v for k, v in actual.dict().items() if k not in ignore} == expected + assert {k: v for k, v in actual.model_dump().items() if k not in ignore} == expected assert actual.file_name_with_ext == f"{expected['file_name']}.{expected['file_extension']}" @@ -128,3 +129,23 @@ def test_submission_info_eq(): # pylint: disable=missing-function-docstring "file_extension": "csv", } assert SubmissionInfo(**data) == SubmissionInfo(**data) # type: ignore + + +class TestAuditRecord: + submission_id = uuid4().hex + + def test_audit_record_just_time_updated(self): + data = { + "submission_id": self.submission_id, + "time_updated": datetime(2025,12,1,12,30,10) + } + + assert AuditRecord(**data).date_updated == date(2025,12,1) + + def test_audit_record_date_updated(self): + data = { + "submission_id": self.submission_id, + "date_updated": date(2025,12,1) + } + + assert AuditRecord(**data).date_updated == date.today() diff --git a/tests/test_model_generation/test_domain_types.py b/tests/test_model_generation/test_domain_types.py index 56cf3f9..8917bac 100644 --- a/tests/test_model_generation/test_domain_types.py +++ b/tests/test_model_generation/test_domain_types.py @@ -15,10 +15,10 @@ class ATestModel(BaseModel): - nhsnumber: Optional[hct.NHSNumber] - postcode: Optional[hct.Postcode] - org_id: Optional[hct.OrgID] - nhsnumber2: Optional[hct.permissive_nhs_number()] + nhsnumber: Optional[hct.NHSNumber] = None + postcode: Optional[hct.Postcode] = None + org_id: Optional[hct.OrgID] = None + nhsnumber2: Optional[hct.permissive_nhs_number()] = None class DatetimeModel(BaseModel): @@ -26,8 +26,8 @@ class DatetimeModel(BaseModel): class ReportingPeriodModel(BaseModel): - reporting_period_start: Optional[hct.reportingperiod(reporting_period_type="start")] - reporting_period_end: Optional[hct.reportingperiod(reporting_period_type="end")] + reporting_period_start: Optional[hct.reportingperiod(reporting_period_type="start")] = None + reporting_period_end: Optional[hct.reportingperiod(reporting_period_type="end")] = None @pytest.mark.parametrize( @@ -256,9 +256,9 @@ def test_formatteddatetime_in_model_raises(datetime_to_validate: Union[str, dt.d class DateModel(BaseModel): """Model for testing FormattedDate.""" - formatted_date: Optional[hct.conformatteddate("%Y-%m-%d")] - formatted_date_constrained: Optional[hct.conformatteddate("%Y-%m-%d", ge="1970-01-01")] - formatted_date_constrained2: Optional[hct.conformatteddate("%Y-%m-%d", lt="1970-01-01")] + formatted_date: Optional[hct.conformatteddate("%Y-%m-%d")] = None + formatted_date_constrained: Optional[hct.conformatteddate("%Y-%m-%d", ge="1970-01-01")] = None + formatted_date_constrained2: Optional[hct.conformatteddate("%Y-%m-%d", lt="1970-01-01")] = None @pytest.mark.parametrize( diff --git a/tests/test_pipeline/test_spark_pipeline.py b/tests/test_pipeline/test_spark_pipeline.py index b3048a1..974f4ac 100644 --- a/tests/test_pipeline/test_spark_pipeline.py +++ b/tests/test_pipeline/test_spark_pipeline.py @@ -166,7 +166,7 @@ def test_apply_data_contract_failed( # pylint: disable=redefined-outer-name "Key": "", "FailureType": "record", "Status": "error", - "ErrorType": "value_error.any_str.max_length", + "ErrorType": "string_too_long", "ErrorLocation": "planet", "ErrorMessage": "is invalid", "ErrorCode": "BadValue", @@ -180,7 +180,7 @@ def test_apply_data_contract_failed( # pylint: disable=redefined-outer-name "Key": "", "FailureType": "record", "Status": "error", - "ErrorType": "value_error.number.not_ge", + "ErrorType": "greater_than_equal", "ErrorLocation": "numberOfMoons", "ErrorMessage": "is invalid", "ErrorCode": "BadValue", @@ -194,7 +194,7 @@ def test_apply_data_contract_failed( # pylint: disable=redefined-outer-name "Key": "", "FailureType": "record", "Status": "error", - "ErrorType": "type_error.bool", + "ErrorType": "bool_parsing", "ErrorLocation": "hasGlobalMagneticField", "ErrorMessage": "is invalid", "ErrorCode": "BadValue", @@ -421,7 +421,7 @@ def test_error_report_where_report_is_expected( # pylint: disable=redefined-out "number_warnings": 0, } - sub_stats = stats.dict() + sub_stats = stats.model_dump() if stats else {} assert all([expected.get(key) == sub_stats.get(key) for key in expected])