From cf417ac019e3dd5934ffd2021eb49ddbdee3f32a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20Tvrtkovi=C4=87?= Date: Tue, 7 Feb 2023 13:40:21 +0100 Subject: [PATCH 1/8] TypedDicts WIP --- poetry.lock | 372 +++++++++------------- src/cattrs/_compat.py | 5 + src/cattrs/converters.py | 29 +- src/cattrs/{gen.py => gen/__init__.py} | 153 ++------- src/cattrs/gen/_consts.py | 17 + src/cattrs/gen/_generics.py | 28 ++ src/cattrs/gen/_lc.py | 30 ++ src/cattrs/gen/_shared.py | 30 ++ src/cattrs/gen/typeddicts.py | 425 +++++++++++++++++++++++++ tests/test_typeddicts.py | 122 +++++++ tests/typeddicts.py | 203 ++++++++++++ tests/untyped.py | 3 +- 12 files changed, 1062 insertions(+), 355 deletions(-) rename src/cattrs/{gen.py => gen/__init__.py} (85%) create mode 100644 src/cattrs/gen/_consts.py create mode 100644 src/cattrs/gen/_generics.py create mode 100644 src/cattrs/gen/_lc.py create mode 100644 src/cattrs/gen/_shared.py create mode 100644 src/cattrs/gen/typeddicts.py create mode 100644 tests/test_typeddicts.py create mode 100644 tests/typeddicts.py diff --git a/poetry.lock b/poetry.lock index e4620629..67150a0a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "alabaster" @@ -6,10 +6,10 @@ version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = "*" files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, + {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, + {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] [[package]] @@ -67,37 +67,24 @@ lxml = ["lxml"] [[package]] name = "black" -version = "23.1.0" +version = "22.12.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "black-23.1.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221"}, - {file = "black-23.1.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26"}, - {file = "black-23.1.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b"}, - {file = "black-23.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104"}, - {file = "black-23.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074"}, - {file = "black-23.1.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27"}, - {file = "black-23.1.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648"}, - {file = "black-23.1.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958"}, - {file = "black-23.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a"}, - {file = "black-23.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481"}, - {file = "black-23.1.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad"}, - {file = "black-23.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8"}, - {file = "black-23.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24"}, - {file = "black-23.1.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6"}, - {file = "black-23.1.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd"}, - {file = "black-23.1.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580"}, - {file = "black-23.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468"}, - {file = "black-23.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753"}, - {file = "black-23.1.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651"}, - {file = "black-23.1.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06"}, - {file = "black-23.1.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739"}, - {file = "black-23.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9"}, - {file = "black-23.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555"}, - {file = "black-23.1.0-py3-none-any.whl", hash = "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32"}, - {file = "black-23.1.0.tar.gz", hash = "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac"}, + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, ] [package.dependencies] @@ -300,8 +287,8 @@ files = [ name = "dnspython" version = "2.1.0" description = "DNS toolkit" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" files = [ {file = "dnspython-2.1.0-py3-none-any.whl", hash = "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216"}, @@ -377,14 +364,14 @@ pyflakes = ">=2.5.0,<2.6.0" [[package]] name = "furo" -version = "2023.3.27" +version = "2022.12.7" description = "A clean customisable Sphinx documentation theme." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "furo-2023.3.27-py3-none-any.whl", hash = "sha256:4ab2be254a2d5e52792d0ca793a12c35582dd09897228a6dd47885dabd5c9521"}, - {file = "furo-2023.3.27.tar.gz", hash = "sha256:b99e7867a5cc833b2b34d7230631dd6558c7a29f93071fdbb5709634bb33c5a5"}, + {file = "furo-2022.12.7-py3-none-any.whl", hash = "sha256:7cb76c12a25ef65db85ab0743df907573d03027a33631f17d267e598ebb191f7"}, + {file = "furo-2022.12.7.tar.gz", hash = "sha256:d8008f8efbe7587a97ba533c8b2df1f9c21ee9b3e5cad0d27f61193d38b1a986"}, ] [package.dependencies] @@ -395,14 +382,14 @@ sphinx-basic-ng = "*" [[package]] name = "hypothesis" -version = "6.65.2" +version = "6.62.0" description = "A library for property-based testing" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "hypothesis-6.65.2-py3-none-any.whl", hash = "sha256:26c52473a526b31672d97f88be0b7ac134f0ea180737c71ada2a4d82ad175e92"}, - {file = "hypothesis-6.65.2.tar.gz", hash = "sha256:821279f05fad38575271be3fdfff1e22232613f3d21b3fc991d735057f9b5b47"}, + {file = "hypothesis-6.62.0-py3-none-any.whl", hash = "sha256:e250da77878460f74b53039493a7a18d6fc137b0b77791b382b6a0f4ada9144e"}, + {file = "hypothesis-6.62.0.tar.gz", hash = "sha256:76f1141e8237f6dd0780a171bec5d6aec873208ccc27b5f9753d4cccd8904272"}, ] [package.dependencies] @@ -586,14 +573,14 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markdown-it-py" -version = "2.2.0" +version = "2.1.0" description = "Python port of markdown-it. Markdown parsing, done right!" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, - {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, + {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, + {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, ] [package.dependencies] @@ -601,10 +588,10 @@ mdurl = ">=0.1,<1.0" typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] +benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] +code-style = ["pre-commit (==2.6)"] +compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] +linkify = ["linkify-it-py (>=1.0,<2.0)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] @@ -612,62 +599,52 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.2" +version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, - {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] [[package]] @@ -718,8 +695,8 @@ files = [ name = "msgpack" version = "1.0.4" description = "MessagePack serializer" -category = "main" -optional = true +category = "dev" +optional = false python-versions = "*" files = [ {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, @@ -817,56 +794,56 @@ testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", [[package]] name = "orjson" -version = "3.8.5" +version = "3.8.4" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" files = [ - {file = "orjson-3.8.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:143639b9898b094883481fac37733231da1c2ae3aec78a1dd8d3b58c9c9fceef"}, - {file = "orjson-3.8.5-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:31f43e63e0d94784c55e86bd376df3f80b574bea8c0bc5ecd8041009fa8ec78a"}, - {file = "orjson-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c802ea6d4a0d40f096aceb5e7ef0a26c23d276cb9334e1cadcf256bb090b6426"}, - {file = "orjson-3.8.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf298b55b371c2772420c5ace4d47b0a3ea1253667e20ded3c363160fd0575f6"}, - {file = "orjson-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68cb4a8501a463771d55bb22fc72795ec7e21d71ab083e000a2c3b651b6fb2af"}, - {file = "orjson-3.8.5-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:4f1427952b3bd92bfb63a61b7ffc33a9f54ec6de296fa8d924cbeba089866acb"}, - {file = "orjson-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c0a9f329468c8eb000742455b83546849bcd69495d6baa6e171c7ee8600a47bd"}, - {file = "orjson-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6535d527aa1e4a757a6ce9b61f3dd74edc762e7d2c6991643aae7c560c8440bd"}, - {file = "orjson-3.8.5-cp310-none-win_amd64.whl", hash = "sha256:2eee64c028adf6378dd714c8debc96d5b92b6bb4862debb65ca868e59bac6c63"}, - {file = "orjson-3.8.5-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:f5745ff473dd5c6718bf8c8d5bc183f638b4f3e03c7163ffcda4d4ef453f42ff"}, - {file = "orjson-3.8.5-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:544f1240b295083697027a5093ec66763218ff16f03521d5020e7a436d2e417b"}, - {file = "orjson-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85c9c6bab97a831e7741089057347d99901b4db2451a076ca8adedc7d96297f"}, - {file = "orjson-3.8.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bae7347764e7be6dada980fd071e865544c98317ab61af575c9cc5e1dc7e3fe"}, - {file = "orjson-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c67f6f6e9d26a06b63126112a7bc8d8529df048d31df2a257a8484b76adf3e5d"}, - {file = "orjson-3.8.5-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:758238364142fcbeca34c968beefc0875ffa10aa2f797c82f51cfb1d22d0934e"}, - {file = "orjson-3.8.5-cp311-none-win_amd64.whl", hash = "sha256:cc7579240fb88a626956a6cb4a181a11b62afbc409ce239a7b866568a2412fa2"}, - {file = "orjson-3.8.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:79aa3e47cbbd4eedbbde4f988f766d6cf38ccb51d52cfabfeb6b8d1b58654d25"}, - {file = "orjson-3.8.5-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:2544cd0d089faa862f5a39f508ee667419e3f9e11f119a6b1505cfce0eb26601"}, - {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2be0025ca7e460bcacb250aba8ce0239be62957d58cf34045834cc9302611d3"}, - {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b57bf72902d818506906e49c677a791f90dbd7f0997d60b14bc6c1ce4ce4cf9"}, - {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ae9832a11c6a9efa8c14224e5caf6e35046efd781de14e59eb69ab4e561cf3"}, - {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:0e28330cc6d51741cad0edd1b57caf6c5531aff30afe41402acde0a03246b8ed"}, - {file = "orjson-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:155954d725627b5480e6cc1ca488afb4fa685099a4ace5f5bf21a182fabf6706"}, - {file = "orjson-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ece1b6ef9312df5d5274ca6786e613b7da7de816356e36bcad9ea8a73d15ab71"}, - {file = "orjson-3.8.5-cp37-none-win_amd64.whl", hash = "sha256:6f58d1f0702332496bc1e2d267c7326c851991b62cf6395370d59c47f9890007"}, - {file = "orjson-3.8.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:933f4ab98362f46a59a6d0535986e1f0cae2f6b42435e24a55922b4bc872af0c"}, - {file = "orjson-3.8.5-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:47a7ca236b25a138a74b2cb5169adcdc5b2b8abdf661de438ba65967a2cde9dc"}, - {file = "orjson-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b573ca942c626fcf8a86be4f180b86b2498b18ae180f37b4180c2aced5808710"}, - {file = "orjson-3.8.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a9bab11611d5452efe4ae5315f5eb806f66104c08a089fb84c648d2e8e00f106"}, - {file = "orjson-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee2f5f6476617d01ca166266d70fd5605d3397a41f067022ce04a2e1ced4c8d"}, - {file = "orjson-3.8.5-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:ec0b0b6cd0b84f03537f22b719aca705b876c54ab5cf3471d551c9644127284f"}, - {file = "orjson-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:df3287dc304c8c4556dc85c4ab89eb333307759c1863f95e72e555c0cfce3e01"}, - {file = "orjson-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:09f40add3c2d208e20f8bf185df38f992bf5092202d2d30eced8f6959963f1d5"}, - {file = "orjson-3.8.5-cp38-none-win_amd64.whl", hash = "sha256:232ec1df0d708f74e0dd1fccac1e9a7008cd120d48fe695e8f0c9d80771da430"}, - {file = "orjson-3.8.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:8fba3e7aede3e88a01e94e6fe63d4580162b212e6da27ae85af50a1787e41416"}, - {file = "orjson-3.8.5-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:85e22c358cab170c8604e9edfffcc45dd7b0027ce57ed6bcacb556e8bfbbb704"}, - {file = "orjson-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeab1d8247507a75926adf3ca995c74e91f5db1f168815bf3e774f992ba52b50"}, - {file = "orjson-3.8.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:daaaef15a41e9e8cadc7677cefe00065ae10bce914eefe8da1cd26b3d063970b"}, - {file = "orjson-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ccc9f52cf46bd353c6ae1153eaf9d18257ddc110d135198b0cd8718474685ce"}, - {file = "orjson-3.8.5-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:d48c182c7ff4ea0787806de8a2f9298ca44fd0068ecd5f23a4b2d8e03c745cb6"}, - {file = "orjson-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1848e3b4cc09cc82a67262ae56e2a772b0548bb5a6f9dcaee10dcaaf0a5177b7"}, - {file = "orjson-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38480031bc8add58effe802291e4abf7042ef72ae1a4302efe9a36c8f8bfbfcc"}, - {file = "orjson-3.8.5-cp39-none-win_amd64.whl", hash = "sha256:0e9a1c2e649cbaed410c882cedc8f3b993d8f1426d9327f31762d3f46fe7cc88"}, - {file = "orjson-3.8.5.tar.gz", hash = "sha256:77a3b2bd0c4ef7723ea09081e3329dac568a62463aed127c1501441b07ffc64b"}, + {file = "orjson-3.8.4-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:94841c3418b818c8cda01ad632f0e740dad3a5bcbc38609f1fd163f4bd202ef9"}, + {file = "orjson-3.8.4-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:ffb9367d63a4c71cd0432fc5a4e295b0e89b061e90be9f5ff3ffca301ba2dd84"}, + {file = "orjson-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74c25f71a41a3a213bee02f496abbc9ebc7cb0c8d1e5c1a5ab77c87b427b3f07"}, + {file = "orjson-3.8.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:30294eeb7417eca85572b64278f5c4fdd72049725d7d23da5042a61c1a2298c9"}, + {file = "orjson-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36257f254635da390a9d689214c4cec9ec4ea224c5e2bcdfe97d15a384695be5"}, + {file = "orjson-3.8.4-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:170a041d0d967e348ab086f1c9765846658cb0e807abe98e8320f665b1c67e9b"}, + {file = "orjson-3.8.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:38df475523ca0f3f88b871821a9fb1a1437dad7c637847e0e7378024c7654473"}, + {file = "orjson-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:54ade013afcda2f32f98227f0eb3dba4267c6efe7a8abd93bfec6d7af2546370"}, + {file = "orjson-3.8.4-cp310-none-win_amd64.whl", hash = "sha256:b0a8273ebcf363042e7847faaf67f92178aceb3476e374da5432f9e6772c7a80"}, + {file = "orjson-3.8.4-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:4c3c3571118371a5b88a89a0a157b4e98827d3a540c3f683c59d80e71f66195d"}, + {file = "orjson-3.8.4-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:86f71f2abc24b79e74a13f8d4eebb7d03cad43d6a46e2cad6fd5891423f84773"}, + {file = "orjson-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bff66b99ce8ba3d3b39ce10f27a848b3b68d80f64d77b8118d4648fafd2f964"}, + {file = "orjson-3.8.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49c59a728510a6ef20de59f6262d1415108b323996634d263e4d0d613994fc13"}, + {file = "orjson-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27225338f24a6906d254c4b8b3849135af758af55ae1f43e5ccbebcf2097628a"}, + {file = "orjson-3.8.4-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:0c42fc0ed3f12bc7d0fd61a26899a8ac32deb6622289110b6d7f4d60d47dcff3"}, + {file = "orjson-3.8.4-cp311-none-win_amd64.whl", hash = "sha256:ad6d533ab9ef8b141d6749e7591ee3a3195037c217818e3b3351df1468efc012"}, + {file = "orjson-3.8.4-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:654e219ace2c942f9d8f3a69935991f8c88efb4a85b074ba917960c800a1e5c8"}, + {file = "orjson-3.8.4-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:0b57cc7a94b133140c85c3873d22e7a1f8e0d2489619a7e2e8640f9a9edfbc29"}, + {file = "orjson-3.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:447a4c95f849872ac9af137153e8748b3b35d199c03a3bc618da6a3a23681f96"}, + {file = "orjson-3.8.4-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5d47002a88f60a35901925d4085a313647229950d2818044520eb0e30fc9447b"}, + {file = "orjson-3.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e26422430227e2a8a6d07daee5390fb6510ce67f2b45a1016595e0ca8fb9515b"}, + {file = "orjson-3.8.4-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:77034415ceb563d38b5aa9713b8945faed7a5ddf5553a5e0831cee362b81e0b2"}, + {file = "orjson-3.8.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ecbd6548232ee4d533effcd4dbf5cfc547738ce1791f6204e949f1214c0a0"}, + {file = "orjson-3.8.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b68f2f7f61b2f85623e0cd02eea04ab358cba9a01370fa8f2e96f1b2b83705b7"}, + {file = "orjson-3.8.4-cp37-none-win_amd64.whl", hash = "sha256:8b18925638936104e1c2bdc555b508733ef83c4c9ac96c5ec6666af6df12c22d"}, + {file = "orjson-3.8.4-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:c834eed62fd184d8c0a707f7d1dbaf57815c4865091bfc220182c83321c773f5"}, + {file = "orjson-3.8.4-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6b07c1d76db95706e78e904f2329796d0c26ffed5eaea7a1be753ef7032c30dc"}, + {file = "orjson-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cb431a8168510458fa9d3a1a3ed8f56909187db953138080e8c8ef44484adde"}, + {file = "orjson-3.8.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:64d60e21096de30101348de25f20c516289365c056d0987ec76c74f5073af122"}, + {file = "orjson-3.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceabb527e0641ff23d5ed89f3d9c22f3df2cbe967547d28ea7fdc09c3de1168d"}, + {file = "orjson-3.8.4-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:16a00f10321b4afacd8ecbe239e4ac8c6a77eb442caeffb638c9a49a653dfa2c"}, + {file = "orjson-3.8.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e9c0fb49362e79e3db158b8d307ff53adb4a5bd2706e1a4372393a4d661000fb"}, + {file = "orjson-3.8.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7f72c7a810977b94d0411a5edf3b0eab7bc5763089ad0a0d639e49f651a0d91"}, + {file = "orjson-3.8.4-cp38-none-win_amd64.whl", hash = "sha256:2e6b088d6a1700fad0ce18715ec309bb7efc8ec6275413e1495261a3bbfbc17a"}, + {file = "orjson-3.8.4-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:5d618f30784321361927a65a6da484ef6da218f62c594987bd4b79f97df55036"}, + {file = "orjson-3.8.4-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:42aee49489de6aa3e9b3280c9a124d2f4a984be7a714cd8b13b27634af817521"}, + {file = "orjson-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31f919b370159b3d0c3f38844d66545a4520bc2652c32b9127d02e94f107247e"}, + {file = "orjson-3.8.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5fd174091a24a5ded94eaa141b1c989ce9dd6a7af8eadd525ef93dc5849cb48e"}, + {file = "orjson-3.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e030e8e50e908a7f8ba02b69c5692e30d9687ad184f0b75bfd8cd57df695093a"}, + {file = "orjson-3.8.4-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:22b4c6066c1906b8d2a88cec8e8dc138ba2e08b17971419672734ca3ddab1995"}, + {file = "orjson-3.8.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:90ea42189d81dea219dfda58374e2d9f07e1d17052fa33ade07441e5e50d62d2"}, + {file = "orjson-3.8.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53cc5be7c5c60d7583d71144bdb77171183f015b755ad5ab4a248fc03e61dad7"}, + {file = "orjson-3.8.4-cp39-none-win_amd64.whl", hash = "sha256:c2664d39fa599beb70afe8aa92c7ca905207a0b0aeb7597c038cb42394bd1a0e"}, + {file = "orjson-3.8.4.tar.gz", hash = "sha256:9dcb8d788254936de2791d6d2c29bc41a3dc2c9d9cb4f01c06c0e6d424844593"}, ] [[package]] @@ -883,14 +860,14 @@ files = [ [[package]] name = "pathspec" -version = "0.11.0" +version = "0.10.3" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, - {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, + {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, + {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, ] [[package]] @@ -966,33 +943,6 @@ importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "psutil" -version = "5.9.4" -description = "Cross-platform lib for process and system monitoring in Python." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, - {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, - {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, - {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, - {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, - {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, - {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, - {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, - {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, - {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, - {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, - {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, - {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, - {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, -] - -[package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] - [[package]] name = "py" version = "1.11.0" @@ -1060,8 +1010,8 @@ plugins = ["importlib-metadata"] name = "pymongo" version = "4.3.3" description = "Python driver for MongoDB " -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" files = [ {file = "pymongo-4.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:74731c9e423c93cbe791f60c27030b6af6a948cef67deca079da6cd1bb583a8e"}, @@ -1153,32 +1103,26 @@ zstd = ["zstandard"] [[package]] name = "pyperf" -version = "2.6.0" +version = "2.5.0" description = "Python module to run and analyze benchmarks" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = "*" files = [ - {file = "pyperf-2.6.0-py3-none-any.whl", hash = "sha256:3e95511cf0c39d68e9e55716ef1b582e7fc1949ec831ef710566b733b44aebaf"}, - {file = "pyperf-2.6.0.tar.gz", hash = "sha256:d7e367a1ec7035d7a2b25f55a5925596c00cb15851f28cffd85b05b7307232af"}, + {file = "pyperf-2.5.0-py3-none-any.whl", hash = "sha256:41633bf3e55094834a803f9be612e5658bab4df6215c324de91d93ec96f16cef"}, + {file = "pyperf-2.5.0.tar.gz", hash = "sha256:9fd9be5b57224e68b5a5b88f7126f15b6c8667573f62a0a39faf14d6fdd13909"}, ] -[package.dependencies] -psutil = ">=5.9.0" - -[package.extras] -dev = ["importlib-metadata", "tox"] - [[package]] name = "pytest" -version = "7.2.1" +version = "7.2.0" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, - {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, + {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, + {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, ] [package.dependencies] @@ -1232,14 +1176,14 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2022.7.1" +version = "2022.7" description = "World timezone definitions, modern and historical" category = "dev" optional = false python-versions = "*" files = [ - {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, - {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, + {file = "pytz-2022.7-py2.py3-none-any.whl", hash = "sha256:93007def75ae22f7cd991c84e02d434876818661f8df9ad5df9e950ff4e52cfd"}, + {file = "pytz-2022.7.tar.gz", hash = "sha256:7ccfae7b4b2c067464a6733c6261673fdb8fd1be905460396b97a073e9fa683a"}, ] [[package]] @@ -1258,7 +1202,7 @@ files = [ name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1269,13 +1213,6 @@ files = [ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, @@ -1558,8 +1495,8 @@ files = [ name = "tomlkit" version = "0.11.6" description = "Style preserving TOML library" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" files = [ {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, @@ -1643,8 +1580,8 @@ files = [ name = "ujson" version = "5.7.0" description = "Ultra fast JSON encoder and decoder for Python" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" files = [ {file = "ujson-5.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5eba5e69e4361ac3a311cf44fa71bc619361b6e0626768a494771aacd1c2f09b"}, @@ -1716,14 +1653,14 @@ files = [ [[package]] name = "urllib3" -version = "1.26.14" +version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, - {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, + {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, + {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, ] [package.extras] @@ -1755,30 +1692,21 @@ testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7 [[package]] name = "zipp" -version = "3.12.0" +version = "3.11.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "zipp-3.12.0-py3-none-any.whl", hash = "sha256:9eb0a4c5feab9b08871db0d672745b53450d7f26992fd1e4653aa43345e97b86"}, - {file = "zipp-3.12.0.tar.gz", hash = "sha256:73efd63936398aac78fd92b6f4865190119d6c91b531532e798977ea8dd402eb"}, + {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, + {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] -[extras] -bson = ["pymongo"] -cbor2 = ["cbor2"] -msgpack = ["msgpack"] -orjson = ["orjson"] -pyyaml = ["PyYAML"] -tomlkit = ["tomlkit"] -ujson = ["ujson"] - [metadata] lock-version = "2.0" python-versions = ">= 3.7" -content-hash = "f1917ee3a777dae10bb5db429c4aac4e597011e189c639011298ece395c2eea7" +content-hash = "9c3822c11c795dc6f7cb06cf4011f64f888ff599375c1298a2c1691efb451683" diff --git a/src/cattrs/_compat.py b/src/cattrs/_compat.py index 18106eff..f3251207 100644 --- a/src/cattrs/_compat.py +++ b/src/cattrs/_compat.py @@ -243,13 +243,18 @@ def copy_with(type, args): from typing import Annotated from typing import Counter as TypingCounter from typing import ( + TypedDict, Union, _AnnotatedAlias, _GenericAlias, _SpecialGenericAlias, + _TypedDictMeta, _UnionGenericAlias, ) + def is_typeddict(cls) -> bool: + return cls.__class__ is _TypedDictMeta + try: # Not present on 3.9.0, so we try carefully. from typing import _LiteralGenericAlias diff --git a/src/cattrs/converters.py b/src/cattrs/converters.py index 64016c73..6c2f47fa 100644 --- a/src/cattrs/converters.py +++ b/src/cattrs/converters.py @@ -22,12 +22,6 @@ from attr import has as attrs_has from attr import resolve_types -from cattrs.errors import ( - IterableValidationError, - IterableValidationNote, - StructureHandlerNotFoundError, -) - from ._compat import ( FrozenSetSubscriptable, Mapping, @@ -56,6 +50,7 @@ is_protocol, is_sequence, is_tuple, + is_typeddict, is_union_type, ) from .disambiguators import create_uniq_field_dis_func @@ -74,6 +69,8 @@ make_mapping_structure_fn, make_mapping_unstructure_fn, ) +from .gen.typeddicts import make_dict_structure_fn as make_typeddict_dict_struct_fn +from .gen.typeddicts import make_dict_unstructure_fn as make_typeddict_dict_unstruct_fn NoneType = type(None) T = TypeVar("T") @@ -865,6 +862,9 @@ def __init__( is_frozenset, lambda cl: self.gen_unstructure_iterable(cl, unstructure_to=frozenset), ) + self.register_unstructure_hook_factory( + is_typeddict, self.gen_unstructure_typeddict + ) self.register_unstructure_hook_factory( lambda t: get_newtype_base(t) is not None, lambda t: self._unstructure_func.dispatch(get_newtype_base(t)), @@ -872,6 +872,7 @@ def __init__( self.register_structure_hook_factory(is_annotated, self.gen_structure_annotated) self.register_structure_hook_factory(is_mapping, self.gen_structure_mapping) self.register_structure_hook_factory(is_counter, self.gen_structure_counter) + self.register_structure_hook_factory(is_typeddict, self.gen_structure_typeddict) self.register_structure_hook_factory( lambda t: get_newtype_base(t) is not None, self.get_structure_newtype ) @@ -895,6 +896,13 @@ def gen_structure_annotated(self, type): h = self._structure_func.dispatch(origin) return h + def gen_unstructure_typeddict(self, cl: Any) -> Callable[[Dict], Dict]: + """Generate a TypedDict unstructure function. + + Also apply converter-scored modifications. + """ + return make_typeddict_dict_unstruct_fn(cl, self) + def gen_unstructure_attrs_fromdict( self, cl: Type[T] ) -> Callable[[T], Dict[str, Any]]: @@ -914,6 +922,15 @@ def gen_unstructure_attrs_fromdict( ) return h + def gen_structure_typeddict(self, cl: Any) -> Callable[[Dict], Dict]: + """Generate a TypedDict structure function. + + Also apply converter-scored modifications. + """ + return make_typeddict_dict_struct_fn( + cl, self, _cattrs_detailed_validation=self.detailed_validation + ) + def gen_structure_attrs_fromdict( self, cl: Type[T] ) -> Callable[[Mapping[str, Any], Any], T]: diff --git a/src/cattrs/gen.py b/src/cattrs/gen/__init__.py similarity index 85% rename from src/cattrs/gen.py rename to src/cattrs/gen/__init__.py index 6bb4effd..f8674db4 100644 --- a/src/cattrs/gen.py +++ b/src/cattrs/gen/__init__.py @@ -2,9 +2,7 @@ import linecache import re -import uuid from dataclasses import is_dataclass -from threading import local from typing import ( TYPE_CHECKING, Any, @@ -19,18 +17,9 @@ ) import attr -from attr import NOTHING, Attribute, frozen, resolve_types +from attr import NOTHING, resolve_types -from cattrs.errors import ( - AttributeValidationNote, - ClassValidationError, - ForbiddenExtraKeysError, - IterableValidationError, - IterableValidationNote, - StructureHandlerNotFoundError, -) - -from ._compat import ( +from .._compat import ( adapted_fields, get_args, get_origin, @@ -39,21 +28,24 @@ is_bare_final, is_generic, ) -from ._generics import deep_copy_with +from .._generics import deep_copy_with +from ..errors import ( + AttributeValidationNote, + ClassValidationError, + ForbiddenExtraKeysError, + IterableValidationError, + IterableValidationNote, + StructureHandlerNotFoundError, +) +from ._consts import AttributeOverride, already_generating, neutral +from ._generics import generate_mapping +from ._lc import generate_unique_filename +from ._shared import find_structure_handler if TYPE_CHECKING: # pragma: no cover from cattr.converters import BaseConverter -@frozen -class AttributeOverride: - omit_if_default: Optional[bool] = None - rename: Optional[str] = None - omit: bool = False # Omit the field completely. - struct_hook: Optional[Callable[[Any, Any], Any]] = None # Structure hook to use. - unstruct_hook: Optional[Callable[[Any], Any]] = None # Structure hook to use. - - def override( omit_if_default: Optional[bool] = None, rename: Optional[str] = None, @@ -64,8 +56,6 @@ def override( return AttributeOverride(omit_if_default, rename, omit, struct_hook, unstruct_hook) -_neutral = AttributeOverride() -_already_generating = local() T = TypeVar("T") @@ -89,11 +79,11 @@ def make_dict_unstructure_fn( mapping = {} if is_generic(cl): - mapping = _generate_mapping(cl, mapping) + mapping = generate_mapping(cl, mapping) for base in getattr(origin, "__orig_bases__", ()): if is_generic(base) and not str(base).startswith("typing.Generic"): - mapping = _generate_mapping(base, mapping) + mapping = generate_mapping(base, mapping) break cl = origin @@ -107,10 +97,10 @@ def make_dict_unstructure_fn( # We keep track of what we're generating to help with recursive # class graphs. try: - working_set = _already_generating.working_set + working_set = already_generating.working_set except AttributeError: working_set = set() - _already_generating.working_set = working_set + already_generating.working_set = working_set if cl in working_set: raise RecursionError() else: @@ -119,7 +109,7 @@ def make_dict_unstructure_fn( try: for a in attrs: attr_name = a.name - override = kwargs.pop(attr_name, _neutral) + override = kwargs.pop(attr_name, neutral) if override.omit: continue kn = attr_name if override.rename is None else override.rename @@ -211,7 +201,7 @@ def make_dict_unstructure_fn( ) script = "\n".join(total_lines) - fname = _generate_unique_filename( + fname = generate_unique_filename( cl, "unstructure", reserve=_cattrs_use_linecache ) @@ -226,69 +216,6 @@ def make_dict_unstructure_fn( return fn -def _generate_mapping(cl: Type, old_mapping: Dict[str, type]) -> Dict[str, type]: - mapping = {} - - # To handle the cases where classes in the typing module are using - # the GenericAlias structure but aren’t a Generic and hence - # end up in this function but do not have an `__parameters__` - # attribute. These classes are interface types, for example - # `typing.Hashable`. - parameters = getattr(get_origin(cl), "__parameters__", None) - if parameters is None: - return old_mapping - - for p, t in zip(parameters, get_args(cl)): - if isinstance(t, TypeVar): - continue - mapping[p.__name__] = t - - if not mapping: - return old_mapping - - return mapping - - -def find_structure_handler( - a: Attribute, type: Any, c: BaseConverter, prefer_attrs_converters: bool = False -) -> Optional[Callable[[Any, Any], Any]]: - """Find the appropriate structure handler to use. - - Return `None` if no handler should be used. - """ - if a.converter is not None and prefer_attrs_converters: - # If the user as requested to use attrib converters, use nothing - # so it falls back to that. - handler = None - elif a.converter is not None and not prefer_attrs_converters and type is not None: - handler = c._structure_func.dispatch(type) - if handler == c._structure_error: - handler = None - elif type is not None: - if ( - is_bare_final(type) - and a.default is not NOTHING - and not isinstance(a.default, attr.Factory) - ): - # This is a special case where we can use the - # type of the default to dispatch on. - type = a.default.__class__ - handler = c._structure_func.dispatch(type) - if handler == c._structure_call: - # Finals can't really be used with _structure_call, so - # we wrap it so the rest of the toolchain doesn't get - # confused. - - def handler(v, _, _h=handler): - return _h(v, type) - - else: - handler = c._structure_func.dispatch(type) - else: - handler = c.structure - return handler - - DictStructureFn = Callable[[Mapping[str, Any], Any], T] @@ -306,12 +233,12 @@ def make_dict_structure_fn( mapping = {} if is_generic(cl): base = get_origin(cl) - mapping = _generate_mapping(cl, mapping) + mapping = generate_mapping(cl, mapping) cl = base for base in getattr(cl, "__orig_bases__", ()): if is_generic(base) and not str(base).startswith("typing.Generic"): - mapping = _generate_mapping(base, mapping) + mapping = generate_mapping(base, mapping) break if isinstance(cl, TypeVar): @@ -363,7 +290,7 @@ def make_dict_structure_fn( internal_arg_parts["__c_avn"] = AttributeValidationNote for a in attrs: an = a.name - override = kwargs.get(an, _neutral) + override = kwargs.get(an, neutral) if override.omit: continue t = a.type @@ -441,7 +368,7 @@ def make_dict_structure_fn( # The first loop deals with required args. for a in attrs: an = a.name - override = kwargs.get(an, _neutral) + override = kwargs.get(an, neutral) if override.omit: continue if a.default is not NOTHING: @@ -492,7 +419,7 @@ def make_dict_structure_fn( for a in non_required: an = a.name - override = kwargs.get(an, _neutral) + override = kwargs.get(an, neutral) t = a.type if isinstance(t, TypeVar): t = mapping.get(t.__name__, t) @@ -554,7 +481,7 @@ def make_dict_structure_fn( + instantiation_lines ) - fname = _generate_unique_filename(cl, "structure", reserve=_cattrs_use_linecache) + fname = generate_unique_filename(cl, "structure", reserve=_cattrs_use_linecache) script = "\n".join(total_lines) eval(compile(script, fname, "exec"), globs) if _cattrs_use_linecache: @@ -825,29 +752,3 @@ def make_mapping_structure_fn( fn = globs[fn_name] return fn - - -def _generate_unique_filename(cls: Any, func_name: str, reserve: bool = True) -> str: - """ - Create a "filename" suitable for a function being generated. - """ - unique_id = uuid.uuid4() - extra = "" - count = 1 - - while True: - unique_filename = "".format( - func_name, cls.__module__, getattr(cls, "__qualname__", cls.__name__), extra - ) - if not reserve: - return unique_filename - # To handle concurrency we essentially "reserve" our spot in - # the linecache with a dummy line. The caller can then - # set this value correctly. - cache_line = (1, None, (str(unique_id),), unique_filename) - if linecache.cache.setdefault(unique_filename, cache_line) == cache_line: - return unique_filename - - # Looks like this spot is taken. Try again. - count += 1 - extra = "-{0}".format(count) diff --git a/src/cattrs/gen/_consts.py b/src/cattrs/gen/_consts.py new file mode 100644 index 00000000..bac7c404 --- /dev/null +++ b/src/cattrs/gen/_consts.py @@ -0,0 +1,17 @@ +from threading import local +from typing import Any, Callable, Optional + +from attr import frozen + + +@frozen +class AttributeOverride: + omit_if_default: Optional[bool] = None + rename: Optional[str] = None + omit: bool = False # Omit the field completely. + struct_hook: Optional[Callable[[Any, Any], Any]] = None # Structure hook to use. + unstruct_hook: Optional[Callable[[Any], Any]] = None # Structure hook to use. + + +neutral = AttributeOverride() +already_generating = local() diff --git a/src/cattrs/gen/_generics.py b/src/cattrs/gen/_generics.py new file mode 100644 index 00000000..b90e494e --- /dev/null +++ b/src/cattrs/gen/_generics.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import Dict, Type, TypeVar + +from .._compat import get_args, get_origin + + +def generate_mapping(cl: Type, old_mapping: Dict[str, type]) -> Dict[str, type]: + mapping = {} + + # To handle the cases where classes in the typing module are using + # the GenericAlias structure but aren’t a Generic and hence + # end up in this function but do not have an `__parameters__` + # attribute. These classes are interface types, for example + # `typing.Hashable`. + parameters = getattr(get_origin(cl), "__parameters__", None) + if parameters is None: + return old_mapping + + for p, t in zip(parameters, get_args(cl)): + if isinstance(t, TypeVar): + continue + mapping[p.__name__] = t + + if not mapping: + return old_mapping + + return mapping diff --git a/src/cattrs/gen/_lc.py b/src/cattrs/gen/_lc.py new file mode 100644 index 00000000..3eb5df75 --- /dev/null +++ b/src/cattrs/gen/_lc.py @@ -0,0 +1,30 @@ +"""Line-cache functionality.""" +import linecache +import uuid +from typing import Any + + +def generate_unique_filename(cls: Any, func_name: str, reserve: bool = True) -> str: + """ + Create a "filename" suitable for a function being generated. + """ + unique_id = uuid.uuid4() + extra = "" + count = 1 + + while True: + unique_filename = "".format( + func_name, cls.__module__, getattr(cls, "__qualname__", cls.__name__), extra + ) + if not reserve: + return unique_filename + # To handle concurrency we essentially "reserve" our spot in + # the linecache with a dummy line. The caller can then + # set this value correctly. + cache_line = (1, None, (str(unique_id),), unique_filename) + if linecache.cache.setdefault(unique_filename, cache_line) == cache_line: + return unique_filename + + # Looks like this spot is taken. Try again. + count += 1 + extra = "-{0}".format(count) diff --git a/src/cattrs/gen/_shared.py b/src/cattrs/gen/_shared.py new file mode 100644 index 00000000..7fc6cb26 --- /dev/null +++ b/src/cattrs/gen/_shared.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Callable, Optional + +from attr import Attribute + +if TYPE_CHECKING: # pragma: no cover + from cattr.converters import BaseConverter + + +def find_structure_handler( + a: Attribute, type: Any, c: BaseConverter, prefer_attrs_converters: bool = False +) -> Optional[Callable[[Any, Any], Any]]: + """Find the appropriate structure handler to use. + + Return `None` if no handler should be used. + """ + if a.converter is not None and prefer_attrs_converters: + # If the user as requested to use attrib converters, use nothing + # so it falls back to that. + handler = None + elif a.converter is not None and not prefer_attrs_converters and type is not None: + handler = c._structure_func.dispatch(type) + if handler == c._structure_error: + handler = None + elif type is not None: + handler = c._structure_func.dispatch(type) + else: + handler = c.structure + return handler diff --git a/src/cattrs/gen/typeddicts.py b/src/cattrs/gen/typeddicts.py new file mode 100644 index 00000000..d98246f0 --- /dev/null +++ b/src/cattrs/gen/typeddicts.py @@ -0,0 +1,425 @@ +from __future__ import annotations + +import linecache +import re +from inspect import get_annotations +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Type, TypeVar + +from attr import NOTHING, Attribute + +from .._compat import get_origin, is_annotated, is_bare, is_generic +from .._generics import deep_copy_with +from ..errors import ClassValidationError, StructureHandlerNotFoundError +from . import AttributeOverride +from ._consts import already_generating, neutral +from ._generics import generate_mapping +from ._lc import generate_unique_filename +from ._shared import find_structure_handler + +if TYPE_CHECKING: # pragma: no cover + from cattr.converters import BaseConverter + +T = TypeVar("T") + + +def make_dict_unstructure_fn( + cl: Type[T], + converter: BaseConverter, + _cattrs_use_linecache: bool = True, + **kwargs: AttributeOverride, +) -> Callable[[T], Dict[str, Any]]: + """ + Generate a specialized dict unstructuring function for a TypedDict. + """ + origin = get_origin(cl) + attrs = adapted_fields(origin or cl) # type: ignore + req_keys = (origin or cl).__required_keys__ + + mapping = {} + if is_generic(cl): + mapping = generate_mapping(cl, mapping) + + for base in getattr(origin, "__orig_bases__", ()): + if is_generic(base) and not str(base).startswith("typing.Generic"): + mapping = generate_mapping(base, mapping) + break + cl = origin + + cl_name = cl.__name__ + fn_name = "unstructure_typeddict_" + cl_name + globs = {} + lines = [] + internal_arg_parts = {} + + # We keep track of what we're generating to help with recursive + # class graphs. + try: + working_set = already_generating.working_set + except AttributeError: + working_set = set() + already_generating.working_set = working_set + if cl in working_set: + raise RecursionError() + else: + working_set.add(cl) + + try: + # We want to short-circuit in certain cases and return the identity + # function. + # We short-circuit if all of these are true: + # * no attributes have been overridden + # * all attributes resolve to `converter._unstructure_identity` + for a in attrs: + attr_name = a.name + override = kwargs.get(attr_name, neutral) + if override != neutral: + break + handler = None + t = a.type + + if isinstance(t, TypeVar): + if t.__name__ in mapping: + t = mapping[t.__name__] + else: + handler = converter.unstructure + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping) + + if handler is None: + try: + handler = converter._unstructure_func.dispatch(t) + except RecursionError: + # There's a circular reference somewhere down the line + handler = converter.unstructure + is_identity = handler == converter._unstructure_identity + if not is_identity: + break + else: + # We've not broken the loop. + return converter._unstructure_identity + + for a in attrs: + attr_name = a.name + override = kwargs.get(attr_name, neutral) + if override.omit: + continue + kn = attr_name if override.rename is None else override.rename + attr_required = attr_name in req_keys + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + handler = None + if override.unstruct_hook is not None: + handler = override.unstruct_hook + else: + t = a.type + if isinstance(t, TypeVar): + if t.__name__ in mapping: + t = mapping[t.__name__] + else: + handler = converter.unstructure + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping) + + if handler is None: + try: + handler = converter._unstructure_func.dispatch(t) + except RecursionError: + # There's a circular reference somewhere down the line + handler = converter.unstructure + + is_identity = handler == converter._unstructure_identity + + if not is_identity: + unstruct_handler_name = f"__c_unstr_{attr_name}" + globs[unstruct_handler_name] = handler + internal_arg_parts[unstruct_handler_name] = handler + invoke = f"{unstruct_handler_name}(instance['{attr_name}'])" + else: + # We're not doing anything to this attribute, so + # it'll already be present in the input dict. + continue + + if attr_required: + # No default or no override. + lines.append(f" res['{kn}'] = {invoke}") + else: + lines.append(f" if '{kn}' in instance: res['{kn}'] = {invoke}") + + internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) + if internal_arg_line: + internal_arg_line = f", {internal_arg_line}" + for k, v in internal_arg_parts.items(): + globs[k] = v + + total_lines = ( + [f"def {fn_name}(instance{internal_arg_line}):"] + + [" res = instance.copy()"] + + lines + + [" return res"] + ) + script = "\n".join(total_lines) + + fname = generate_unique_filename( + cl, "unstructure", reserve=_cattrs_use_linecache + ) + + eval(compile(script, fname, "exec"), globs) + + fn = globs[fn_name] + if _cattrs_use_linecache: + linecache.cache[fname] = len(script), None, total_lines, fname + finally: + working_set.remove(cl) + + return fn + + +def make_dict_structure_fn( + cl: Any, + converter: BaseConverter, + _cattrs_use_linecache: bool = True, + _cattrs_detailed_validation: bool = True, + **kwargs: AttributeOverride, +) -> Callable[[Dict, Any], Any]: + """Generate a specialized dict structuring function for typed dicts.""" + + mapping = {} + if is_generic(cl): + base = get_origin(cl) + mapping = generate_mapping(cl, mapping) + cl = base + + for base in getattr(cl, "__orig_bases__", ()): + if is_generic(base) and not str(base).startswith("typing.Generic"): + mapping = generate_mapping(base, mapping) + break + + if isinstance(cl, TypeVar): + cl = mapping.get(cl.__name__, cl) + + cl_name = cl.__name__ + fn_name = "structure_" + cl_name + req_keys = cl.__required_keys__ + + # We have generic parameters and need to generate a unique name for the function + for p in getattr(cl, "__parameters__", ()): + # This is nasty, I am not sure how best to handle `typing.List[str]` or `TClass[int, int]` as a parameter type here + try: + name_base = mapping[p.__name__] + except KeyError: + raise StructureHandlerNotFoundError( + f"Missing type for generic argument {p.__name__}, specify it when structuring.", + p, + ) from None + name = getattr(name_base, "__name__", None) or str(name_base) + # `<>` can be present in lambdas + # `|` can be present in unions + name = re.sub(r"[\[\.\] ,<>]", "_", name) + name = re.sub(r"\|", "u", name) + fn_name += f"_{name}" + + internal_arg_parts = {"__cl": cl} + globs = {} + lines = [] + post_lines = [] + + attrs = adapted_fields(cl) + + allowed_fields = set() + # if _cattrs_forbid_extra_keys: + # globs["__c_a"] = allowed_fields + # globs["__c_feke"] = ForbiddenExtraKeysError + + lines.append(" res = o.copy()") + + if _cattrs_detailed_validation: + lines.append(" errors = []") + internal_arg_parts["__c_cve"] = ClassValidationError + for a in attrs: + an = a.name + attr_required = an in req_keys + override = kwargs.get(an, neutral) + if override.omit: + continue + t = a.type + if isinstance(t, TypeVar): + t = mapping.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping) + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + if override.struct_hook is not None: + # If the user has requested an override, just use that. + handler = override.struct_hook + else: + handler = find_structure_handler(a, t, converter) + + struct_handler_name = f"__c_structure_{an}" + internal_arg_parts[struct_handler_name] = handler + + kn = an if override.rename is None else override.rename + allowed_fields.add(kn) + i = " " + if not attr_required: + lines.append(f"{i}if '{kn}' in o:") + i = f"{i} " + lines.append(f"{i}try:") + i = f"{i} " + if handler: + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + lines.append(f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'])") + else: + type_name = f"__c_type_{an}" + internal_arg_parts[type_name] = t + lines.append( + f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'], {type_name})" + ) + else: + lines.append(f"{i}res['{an}'] = o['{kn}']") + i = i[:-2] + lines.append(f"{i}except Exception as e:") + i = f"{i} " + lines.append( + f"{i}e.__notes__ = getattr(e, '__notes__', []) + [\"Structuring class {cl.__qualname__} @ attribute {an}\"]" + ) + lines.append(f"{i}errors.append(e)") + + # if _cattrs_forbid_extra_keys: + # post_lines += [ + # " unknown_fields = set(o.keys()) - __c_a", + # " if unknown_fields:", + # " errors.append(__c_feke('', __cl, unknown_fields))", + # ] + + post_lines.append( + f" if errors: raise __c_cve('While structuring ' + {cl.__name__!r}, errors, __cl)" + ) + else: + non_required = [] + + # The first loop deals with required args. + for a in attrs: + an = a.name + attr_required = an in req_keys + override = kwargs.get(an, neutral) + if override.omit: + continue + if not attr_required: + non_required.append(a) + continue + t = a.type + if isinstance(t, TypeVar): + t = mapping.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping) + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + if t is not None: + handler = converter._structure_func.dispatch(t) + else: + handler = converter.structure + + kn = an if override.rename is None else override.rename + allowed_fields.add(kn) + + if handler: + struct_handler_name = f"__c_structure_{an}" + internal_arg_parts[struct_handler_name] = handler + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + invocation_line = ( + f" res['{kn}'] = {struct_handler_name}(o['{kn}'])" + ) + else: + type_name = f"__c_type_{an}" + internal_arg_parts[type_name] = t + invocation_line = ( + f" res['{kn}'] = {struct_handler_name}(o['{kn}'], {type_name})" + ) + else: + invocation_line = f" res['{kn}'] = o['{kn}']" + + lines.append(invocation_line) + + # The second loop is for optional args. + if non_required: + for a in non_required: + an = a.name + override = kwargs.get(an, neutral) + t = a.type + if isinstance(t, TypeVar): + t = mapping.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping) + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + if t is not None: + handler = converter._structure_func.dispatch(t) + else: + handler = converter.structure + + struct_handler_name = f"__c_structure_{an}" + internal_arg_parts[struct_handler_name] = handler + + ian = an + kn = an if override.rename is None else override.rename + allowed_fields.add(kn) + post_lines.append(f" if '{kn}' in o:") + if handler: + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + post_lines.append( + f" res['{ian}'] = {struct_handler_name}(o['{kn}'])" + ) + else: + type_name = f"__c_type_{an}" + internal_arg_parts[type_name] = t + post_lines.append( + f" res['{ian}'] = {struct_handler_name}(o['{kn}'], {type_name})" + ) + else: + post_lines.append(f" res['{ian}'] = o['{kn}']") + + # if _cattrs_forbid_extra_keys: + # post_lines += [ + # " unknown_fields = set(o.keys()) - __c_a", + # " if unknown_fields:", + # " raise __c_feke('', __cl, unknown_fields)", + # ] + + # At the end, we create the function header. + internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) + for k, v in internal_arg_parts.items(): + globs[k] = v + + total_lines = ( + [f"def {fn_name}(o, _, *, {internal_arg_line}):"] + + lines + + post_lines + + [" return res"] + ) + + fname = generate_unique_filename(cl, "structure", reserve=_cattrs_use_linecache) + script = "\n".join(total_lines) + eval(compile(script, fname, "exec"), globs) + if _cattrs_use_linecache: + linecache.cache[fname] = len(script), None, total_lines, fname + + return globs[fn_name] + + +def adapted_fields(cls: Any) -> List[Attribute]: + annotations = get_annotations(cls, eval_str=True) + return [ + Attribute(n, NOTHING, None, False, False, False, False, False, type=a) + for n, a in annotations.items() + ] diff --git a/tests/test_typeddicts.py b/tests/test_typeddicts.py new file mode 100644 index 00000000..5dca1d66 --- /dev/null +++ b/tests/test_typeddicts.py @@ -0,0 +1,122 @@ +"""Tests for TypedDict un/structuring.""" +from datetime import datetime +from inspect import get_annotations + +from hypothesis import assume, given +from hypothesis.strategies import booleans + +from cattrs import Converter + +from .typeddicts import ( + generic_typeddicts, + simple_typeddicts, + simple_typeddicts_with_extra_keys, +) + + +def mk_converter(detailed_validation: bool = True) -> Converter: + """We can't use function-scoped fixtures with Hypothesis strats.""" + c = Converter(detailed_validation=detailed_validation) + c.register_unstructure_hook(datetime, lambda d: d.timestamp()) + c.register_structure_hook(datetime, lambda d, _: datetime.fromtimestamp(d)) + return c + + +@given(simple_typeddicts()) +def test_simple_roundtrip(cls_and_instance) -> None: + """Round-trips for simple classes work.""" + c = mk_converter() + cls, instance = cls_and_instance + + unstructured = c.unstructure(instance, unstructure_as=cls) + + if all(a is not datetime for _, a in get_annotations(cls).items()): + assert unstructured == instance + + if all(a is int for _, a in get_annotations(cls).items()): + assert unstructured is instance + + restructured = c.structure(unstructured, cls) + + assert restructured is not unstructured + assert restructured == instance + + +@given(simple_typeddicts(total=False), booleans()) +def test_simple_nontotal(cls_and_instance, detailed_validation: bool) -> None: + """Non-total dicts work.""" + c = mk_converter(detailed_validation=detailed_validation) + cls, instance = cls_and_instance + + unstructured = c.unstructure(instance, unstructure_as=cls) + + if all(a is not datetime for _, a in get_annotations(cls).items()): + assert unstructured == instance + + if all(a is int for _, a in get_annotations(cls).items()): + assert unstructured is instance + + restructured = c.structure(unstructured, cls) + + assert restructured is not unstructured + assert restructured == instance + + +@given(simple_typeddicts()) +def test_int_override(cls_and_instance) -> None: + """Overriding a base unstructure handler should work.""" + cls, instance = cls_and_instance + + assume(any(a is int for _, a in get_annotations(cls).items())) + assume(all(a is not datetime for _, a in get_annotations(cls).items())) + + c = mk_converter() + c.register_unstructure_hook(int, lambda i: i) + unstructured = c.unstructure(instance, unstructure_as=cls) + + assert unstructured is not instance + assert unstructured == instance + + +@given(simple_typeddicts_with_extra_keys(), booleans()) +def test_extra_keys( + cls_instance_extra: tuple[type, dict, set[str]], detailed_validation: bool +) -> None: + """Extra keys are preserved.""" + cls, instance, extra = cls_instance_extra + + c = mk_converter(detailed_validation) + + unstructured = c.unstructure(instance, unstructure_as=cls) + for k in extra: + assert k in unstructured + + structured = c.structure(unstructured, cls) + + for k in extra: + assert k in structured + + assert structured == instance + + +@given(generic_typeddicts(total=True), booleans()) +def test_generics( + cls_and_instance: tuple[type, dict], detailed_validation: bool +) -> None: + """Generic TypedDicts work.""" + c = mk_converter(detailed_validation=detailed_validation) + cls, instance = cls_and_instance + + unstructured = c.unstructure(instance, unstructure_as=cls) + print(get_annotations(cls)) + + if all(a is not datetime for _, a in get_annotations(cls).items()): + assert unstructured == instance + + if all(a is int for _, a in get_annotations(cls).items()): + assert unstructured is instance + + restructured = c.structure(unstructured, cls) + + assert restructured is not unstructured + assert restructured == instance diff --git a/tests/typeddicts.py b/tests/typeddicts.py new file mode 100644 index 00000000..f567f2c8 --- /dev/null +++ b/tests/typeddicts.py @@ -0,0 +1,203 @@ +"""Strategies for typed dicts.""" +from datetime import datetime +from string import ascii_lowercase +from typing import Generic, List, Optional, TypedDict, TypeVar + +from attr import NOTHING +from hypothesis.strategies import ( + DrawFn, + SearchStrategy, + booleans, + composite, + datetimes, + integers, + just, + lists, + sets, + text, +) + +from .untyped import gen_attr_names + +# Type aliases for readability +TypedDictType = type +T1 = TypeVar("T1") +T2 = TypeVar("T2") +T3 = TypeVar("T3") + + +@composite +def int_attributes( + draw: DrawFn, total: bool = True +) -> tuple[int, SearchStrategy, SearchStrategy]: + if total: + return int, integers(), text(ascii_lowercase) + else: + return int, integers() | just(NOTHING), text(ascii_lowercase) + + +def datetime_attributes( + total: bool = True, +) -> SearchStrategy[tuple[datetime, SearchStrategy, SearchStrategy]]: + success_strat = datetimes().map(lambda dt: dt.replace(microsecond=0)) + return just( + ( + datetime, + success_strat if total else success_strat | just(NOTHING), + text(ascii_lowercase), + ) + ) + + +@composite +def list_of_int_attributes( + draw: DrawFn, total: bool = True +) -> tuple[List[int], SearchStrategy, SearchStrategy]: + if total: + return List[int], lists(integers()), text(ascii_lowercase).map(lambda v: [v]) + else: + return ( + List[int], + lists(integers()) | just(NOTHING), + text(ascii_lowercase).map(lambda v: [v]), + ) + + +@composite +def simple_typeddicts( + draw: DrawFn, total: Optional[bool] = None +) -> tuple[TypedDictType, dict]: + """Generate simple typed dicts. + + :param total: Generate the given totality dicts (default = random) + """ + if total is None: + total = draw(booleans()) + + attrs = draw( + lists( + int_attributes(total) + | list_of_int_attributes(total) + | datetime_attributes(total) + ) + ) + + attrs_dict = {n: attr[0] for n, attr in zip(gen_attr_names(), attrs)} + success_payload = {} + for n, a in zip(attrs_dict, attrs): + v = draw(a[1]) + if v is not NOTHING: + success_payload[n] = v + + cls = TypedDict("HypTypedDict", attrs_dict, total=total) + + if draw(booleans()): + + class InheritedTypedDict(cls): + inherited: int + + cls = InheritedTypedDict + success_payload["inherited"] = draw(integers()) + + return (cls, success_payload) + + +@composite +def simple_typeddicts_with_extra_keys( + draw: DrawFn, total: Optional[bool] = None +) -> tuple[TypedDictType, dict, set[str]]: + """Generate TypedDicts, with the instances having extra keys.""" + cls, success = draw(simple_typeddicts(total)) + + # The normal attributes are 2 characters or less. + extra_keys = draw(sets(text(ascii_lowercase, min_size=3, max_size=3))) + success.update({k: 1 for k in extra_keys}) + + return cls, success, extra_keys + + +@composite +def generic_typeddicts( + draw: DrawFn, total: Optional[bool] = None +) -> tuple[TypedDictType, dict]: + """Generate generic typed dicts. + + :param total: Generate the given totality dicts (default = random) + """ + if total is None: + total = draw(booleans()) + + attrs = draw( + lists( + int_attributes(total) + | list_of_int_attributes(total) + | datetime_attributes(total), + min_size=1, + ) + ) + + attrs_dict = {n: attr[0] for n, attr in zip(gen_attr_names(), attrs)} + success_payload = {} + for n, a in zip(attrs_dict, attrs): + v = draw(a[1]) + if v is not NOTHING: + success_payload[n] = v + + # We choose up to 3 attributes and make them generic. + generic_attrs = draw( + lists(integers(0, len(attrs) - 1), min_size=1, max_size=3, unique=True) + ) + generics = [] + actual_types = [] + for ix, (attr_name, attr_type) in enumerate(list(attrs_dict.items())): + if ix in generic_attrs: + typevar = TypeVar(f"T{ix+1}") + generics.append(typevar) + actual_types.append(attr_type) + attrs_dict[attr_name] = typevar + + cls = make_typeddict( + "HypTypedDict", attrs_dict, total=total, bases=[Generic[*generics]] + ) + + if draw(booleans()): + + class InheritedTypedDict(cls): + inherited: int + + cls = InheritedTypedDict + success_payload["inherited"] = draw(integers()) + + return (cls[*actual_types], success_payload) + + +def make_typeddict( + cls_name: str, attrs: dict[str, type], total: bool = True, bases: list = [] +) -> TypedDictType: + from inspect import get_annotations + + globs = {"TypedDict": TypedDict} + lines = [] + + bases_snippet = ",".join(f"_base{ix}" for ix in range(len(bases))) + for ix, base in enumerate(bases): + globs[f"_base{ix}"] = base + if bases_snippet: + bases_snippet = f", {bases_snippet}" + + lines.append(f"class {cls_name}(TypedDict{bases_snippet},total={total}):") + for n, t in attrs.items(): + globs[f"_{n}_type"] = t + lines.append(f" {n}: _{n}_type") + + script = "\n".join(lines) + eval(compile(script, "name", "exec"), globs) + + cls = globs[cls_name] + + print(len(attrs)) + print(get_annotations(cls)) + if len(attrs) != len(get_annotations(cls)): + breakpoint() + + return cls diff --git a/tests/untyped.py b/tests/untyped.py index 98ed7d85..2eef7eea 100644 --- a/tests/untyped.py +++ b/tests/untyped.py @@ -6,6 +6,7 @@ from typing import ( Any, Dict, + Iterable, List, Mapping, MutableMapping, @@ -127,7 +128,7 @@ def create_dict_and_type(tuple_of_strats): ) -def gen_attr_names(): +def gen_attr_names() -> Iterable[str]: """ Generate names for attributes, 'a'...'z', then 'aa'...'zz'. ~702 different attribute names should be enough in practice. From 083906d744ccbc767f84a038b3e6e8c6f9980963 Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Sun, 16 Apr 2023 01:55:43 +0200 Subject: [PATCH 2/8] TypedDicts WIP --- src/cattrs/_compat.py | 22 +++++++++++++--- src/cattrs/gen/_generics.py | 49 +++++++++++++++++++++++------------- src/cattrs/gen/typeddicts.py | 12 +++++++-- tests/test_generics.py | 22 ++++++++++++++++ tests/test_typeddicts.py | 48 ++++++++++++++++++++++++++++------- tests/typeddicts.py | 39 +++++++++++++++------------- 6 files changed, 141 insertions(+), 51 deletions(-) diff --git a/src/cattrs/_compat.py b/src/cattrs/_compat.py index f3251207..413bdc83 100644 --- a/src/cattrs/_compat.py +++ b/src/cattrs/_compat.py @@ -103,6 +103,14 @@ def adapted_fields(cl) -> List[Attribute]: return attribs +def is_subclass(obj: type, bases) -> bool: + """A safe version of issubclass (won't raise).""" + try: + return issubclass(obj, bases) + except TypeError: + return False + + def is_hetero_tuple(type: Any) -> bool: origin = getattr(type, "__origin__", None) return origin is tuple and ... not in type.__args__ @@ -243,7 +251,7 @@ def copy_with(type, args): from typing import Annotated from typing import Counter as TypingCounter from typing import ( - TypedDict, + Generic, Union, _AnnotatedAlias, _GenericAlias, @@ -253,7 +261,9 @@ def copy_with(type, args): ) def is_typeddict(cls) -> bool: - return cls.__class__ is _TypedDictMeta + return cls.__class__ is _TypedDictMeta or ( + is_generic(cls) and (cls.__origin__.__class__ is _TypedDictMeta) + ) try: # Not present on 3.9.0, so we try carefully. @@ -393,8 +403,12 @@ def is_counter(type): or getattr(type, "__origin__", None) is Counter ) - def is_generic(obj): - return isinstance(obj, _GenericAlias) or isinstance(obj, GenericAlias) + def is_generic(obj) -> bool: + return ( + isinstance(obj, _GenericAlias) + or isinstance(obj, GenericAlias) + or is_subclass(obj, Generic) + ) def copy_with(type, args): """Replace a generic type's arguments.""" diff --git a/src/cattrs/gen/_generics.py b/src/cattrs/gen/_generics.py index b90e494e..badd2819 100644 --- a/src/cattrs/gen/_generics.py +++ b/src/cattrs/gen/_generics.py @@ -2,27 +2,40 @@ from typing import Dict, Type, TypeVar -from .._compat import get_args, get_origin +from .._compat import get_args, get_origin, is_generic -def generate_mapping(cl: Type, old_mapping: Dict[str, type]) -> Dict[str, type]: +def generate_mapping(cl: Type, old_mapping: Dict[str, type] = {}) -> Dict[str, type]: mapping = {} - # To handle the cases where classes in the typing module are using - # the GenericAlias structure but aren’t a Generic and hence - # end up in this function but do not have an `__parameters__` - # attribute. These classes are interface types, for example - # `typing.Hashable`. - parameters = getattr(get_origin(cl), "__parameters__", None) - if parameters is None: - return old_mapping - - for p, t in zip(parameters, get_args(cl)): - if isinstance(t, TypeVar): - continue - mapping[p.__name__] = t - - if not mapping: - return old_mapping + origin = get_origin(cl) + + if origin is not None: + # To handle the cases where classes in the typing module are using + # the GenericAlias structure but aren’t a Generic and hence + # end up in this function but do not have an `__parameters__` + # attribute. These classes are interface types, for example + # `typing.Hashable`. + parameters = getattr(get_origin(cl), "__parameters__", None) + if parameters is None: + return dict(old_mapping) + + for p, t in zip(parameters, get_args(cl)): + if isinstance(t, TypeVar): + continue + mapping[p.__name__] = t + + if not mapping: + return dict(old_mapping) + elif is_generic(cl): + # Origin is None, so this may be a subclass of a generic class. + orig_bases = cl.__orig_bases__ + for base in orig_bases: + if not hasattr(base, "__args__"): + continue + base_args = base.__args__ + base_params = base.__origin__.__parameters__ + for param, arg in zip(base_params, base_args): + mapping[param.__name__] = arg return mapping diff --git a/src/cattrs/gen/typeddicts.py b/src/cattrs/gen/typeddicts.py index d98246f0..52d4feec 100644 --- a/src/cattrs/gen/typeddicts.py +++ b/src/cattrs/gen/typeddicts.py @@ -43,7 +43,11 @@ def make_dict_unstructure_fn( if is_generic(base) and not str(base).startswith("typing.Generic"): mapping = generate_mapping(base, mapping) break - cl = origin + + # It's possible for origin to be None if this is a subclass + # of a generic class. + if origin is not None: + cl = origin cl_name = cl.__name__ fn_name = "unstructure_typeddict_" + cl_name @@ -99,6 +103,7 @@ def make_dict_unstructure_fn( return converter._unstructure_identity for a in attrs: + print(a) attr_name = a.name override = kwargs.get(attr_name, neutral) if override.omit: @@ -189,7 +194,10 @@ def make_dict_structure_fn( if is_generic(cl): base = get_origin(cl) mapping = generate_mapping(cl, mapping) - cl = base + if base is not None: + # It's possible for this to be a subclass of a generic, + # so no origin. + cl = base for base in getattr(cl, "__orig_bases__", ()): if is_generic(base) and not str(base).startswith("typing.Generic"): diff --git a/tests/test_generics.py b/tests/test_generics.py index a7801b6a..27c9bb58 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -7,6 +7,7 @@ from cattrs._compat import Protocol, is_py39_plus, is_py310_plus from cattrs._generics import deep_copy_with from cattrs.errors import StructureHandlerNotFoundError +from cattrs.gen._generics import generate_mapping from ._compat import Dict_origin, List_origin @@ -264,3 +265,24 @@ class Outer(Generic[T]): raw = c.unstructure(Outer(A(1)), unstructure_as=Outer[A | B]) assert c.structure(raw, Outer[A | B]) == Outer((A(1))) + + +def test_generate_typeddict_mapping() -> None: + from typing import Generic, TypedDict, TypeVar + + T = TypeVar("T") + + class A(TypedDict): + pass + + assert generate_mapping(A, {}) == {} + + class A(TypedDict, Generic[T]): + a: T + + assert generate_mapping(A[int], {}) == {T.__name__: int} + + class B(A[int]): + pass + + assert generate_mapping(B, {}) == {T.__name__: int} diff --git a/tests/test_typeddicts.py b/tests/test_typeddicts.py index 5dca1d66..d546d6a0 100644 --- a/tests/test_typeddicts.py +++ b/tests/test_typeddicts.py @@ -6,6 +6,8 @@ from hypothesis.strategies import booleans from cattrs import Converter +from cattrs._compat import is_generic +from cattrs.gen._generics import generate_mapping from .typeddicts import ( generic_typeddicts, @@ -22,6 +24,30 @@ def mk_converter(detailed_validation: bool = True) -> Converter: return c +def get_annot(t) -> dict: + """Our version, handling type vars properly.""" + if is_generic(t): + # This will have typevars. + origin = getattr(t, "__origin__", None) + if origin is not None: + origin_annotations = get_annotations(origin) + args = t.__args__ + params = origin.__parameters__ + param_to_args = dict(zip(params, args)) + return { + k: param_to_args[v] if v in param_to_args else v + for k, v in origin_annotations.items() + } + else: + # Origin is `None`, so this is a subclass for a generic typeddict. + mapping = generate_mapping(t) + return { + k: mapping[v.__name__] if v.__name__ in mapping else v + for k, v in get_annotations(t).items() + } + return get_annotations(t) + + @given(simple_typeddicts()) def test_simple_roundtrip(cls_and_instance) -> None: """Round-trips for simple classes work.""" @@ -30,10 +56,10 @@ def test_simple_roundtrip(cls_and_instance) -> None: unstructured = c.unstructure(instance, unstructure_as=cls) - if all(a is not datetime for _, a in get_annotations(cls).items()): + if all(a is not datetime for _, a in get_annot(cls).items()): assert unstructured == instance - if all(a is int for _, a in get_annotations(cls).items()): + if all(a is int for _, a in get_annot(cls).items()): assert unstructured is instance restructured = c.structure(unstructured, cls) @@ -50,10 +76,10 @@ def test_simple_nontotal(cls_and_instance, detailed_validation: bool) -> None: unstructured = c.unstructure(instance, unstructure_as=cls) - if all(a is not datetime for _, a in get_annotations(cls).items()): + if all(a is not datetime for _, a in get_annot(cls).items()): assert unstructured == instance - if all(a is int for _, a in get_annotations(cls).items()): + if all(a is int for _, a in get_annot(cls).items()): assert unstructured is instance restructured = c.structure(unstructured, cls) @@ -67,8 +93,8 @@ def test_int_override(cls_and_instance) -> None: """Overriding a base unstructure handler should work.""" cls, instance = cls_and_instance - assume(any(a is int for _, a in get_annotations(cls).items())) - assume(all(a is not datetime for _, a in get_annotations(cls).items())) + assume(any(a is int for _, a in get_annot(cls).items())) + assume(all(a is not datetime for _, a in get_annot(cls).items())) c = mk_converter() c.register_unstructure_hook(int, lambda i: i) @@ -108,15 +134,19 @@ def test_generics( cls, instance = cls_and_instance unstructured = c.unstructure(instance, unstructure_as=cls) - print(get_annotations(cls)) - if all(a is not datetime for _, a in get_annotations(cls).items()): + if all(a is not datetime for _, a in get_annot(cls).items()): assert unstructured == instance - if all(a is int for _, a in get_annotations(cls).items()): + if all(a is int for _, a in get_annot(cls).items()): assert unstructured is instance restructured = c.structure(unstructured, cls) assert restructured is not unstructured assert restructured == instance + + +@given(generic_typeddicts(total=True), booleans()) +def test_not_required() -> None: + pass diff --git a/tests/typeddicts.py b/tests/typeddicts.py index f567f2c8..7ce14ca9 100644 --- a/tests/typeddicts.py +++ b/tests/typeddicts.py @@ -1,7 +1,7 @@ """Strategies for typed dicts.""" from datetime import datetime from string import ascii_lowercase -from typing import Generic, List, Optional, TypedDict, TypeVar +from typing import Generic, List, NotRequired, Optional, Required, TypedDict, TypeVar from attr import NOTHING from hypothesis.strategies import ( @@ -28,12 +28,18 @@ @composite def int_attributes( - draw: DrawFn, total: bool = True + draw: DrawFn, total: bool = True, not_required: bool = False ) -> tuple[int, SearchStrategy, SearchStrategy]: if total: - return int, integers(), text(ascii_lowercase) + if not_required and draw(booleans()): + return (NotRequired[int], integers() | just(NOTHING), text(ascii_lowercase)) + else: + return int, integers(), text(ascii_lowercase) else: - return int, integers() | just(NOTHING), text(ascii_lowercase) + if not_required and draw(booleans()): + return Required[int], integers(), text(ascii_lowercase) + else: + return int, integers() | just(NOTHING), text(ascii_lowercase) def datetime_attributes( @@ -65,7 +71,7 @@ def list_of_int_attributes( @composite def simple_typeddicts( - draw: DrawFn, total: Optional[bool] = None + draw: DrawFn, total: Optional[bool] = None, not_required: bool = False ) -> tuple[TypedDictType, dict]: """Generate simple typed dicts. @@ -76,7 +82,7 @@ def simple_typeddicts( attrs = draw( lists( - int_attributes(total) + int_attributes(total, not_required) | list_of_int_attributes(total) | datetime_attributes(total) ) @@ -118,7 +124,7 @@ def simple_typeddicts_with_extra_keys( @composite def generic_typeddicts( - draw: DrawFn, total: Optional[bool] = None + draw: DrawFn, total: Optional[bool] = None, include_not_required: bool = False ) -> tuple[TypedDictType, dict]: """Generate generic typed dicts. @@ -162,20 +168,20 @@ def generic_typeddicts( if draw(booleans()): - class InheritedTypedDict(cls): + class InheritedTypedDict(cls[*actual_types]): inherited: int cls = InheritedTypedDict success_payload["inherited"] = draw(integers()) + else: + cls = cls[*actual_types] - return (cls[*actual_types], success_payload) + return (cls, success_payload) def make_typeddict( cls_name: str, attrs: dict[str, type], total: bool = True, bases: list = [] ) -> TypedDictType: - from inspect import get_annotations - globs = {"TypedDict": TypedDict} lines = [] @@ -187,17 +193,14 @@ def make_typeddict( lines.append(f"class {cls_name}(TypedDict{bases_snippet},total={total}):") for n, t in attrs.items(): - globs[f"_{n}_type"] = t - lines.append(f" {n}: _{n}_type") + # Strip the initial underscore if present, to prevent mangling. + trimmed = n[1:] if n.startswith("_") else n + globs[f"_{trimmed}_type"] = t + lines.append(f" {n}: _{trimmed}_type") script = "\n".join(lines) eval(compile(script, "name", "exec"), globs) cls = globs[cls_name] - print(len(attrs)) - print(get_annotations(cls)) - if len(attrs) != len(get_annotations(cls)): - breakpoint() - return cls From 679ddc20e7df083d0653bfa4725e2a09d4d899ed Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Mon, 17 Apr 2023 01:22:55 +0200 Subject: [PATCH 3/8] More tests --- poetry.lock | 138 +++++++++++++++++++++++++---------- pyproject.toml | 2 +- src/cattrs/_compat.py | 7 ++ src/cattrs/converters.py | 7 +- src/cattrs/gen/__init__.py | 6 +- src/cattrs/gen/_generics.py | 2 + src/cattrs/gen/_shared.py | 24 +++++- src/cattrs/gen/typeddicts.py | 31 +++++++- tests/test_typeddicts.py | 61 +++++++++++++++- tests/typeddicts.py | 66 ++++++++++++----- 10 files changed, 275 insertions(+), 69 deletions(-) diff --git a/poetry.lock b/poetry.lock index 67150a0a..316d4e38 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -6,10 +6,10 @@ version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, - {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] [[package]] @@ -67,24 +67,37 @@ lxml = ["lxml"] [[package]] name = "black" -version = "22.12.0" +version = "23.3.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, - {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, - {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, - {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, - {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, - {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, - {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, - {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, - {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, - {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, - {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, - {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, + {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, + {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, + {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, + {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, + {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, + {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, + {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, + {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, + {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, + {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, + {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, + {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, + {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, ] [package.dependencies] @@ -287,8 +300,8 @@ files = [ name = "dnspython" version = "2.1.0" description = "DNS toolkit" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.6" files = [ {file = "dnspython-2.1.0-py3-none-any.whl", hash = "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216"}, @@ -364,14 +377,14 @@ pyflakes = ">=2.5.0,<2.6.0" [[package]] name = "furo" -version = "2022.12.7" +version = "2023.3.27" description = "A clean customisable Sphinx documentation theme." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "furo-2022.12.7-py3-none-any.whl", hash = "sha256:7cb76c12a25ef65db85ab0743df907573d03027a33631f17d267e598ebb191f7"}, - {file = "furo-2022.12.7.tar.gz", hash = "sha256:d8008f8efbe7587a97ba533c8b2df1f9c21ee9b3e5cad0d27f61193d38b1a986"}, + {file = "furo-2023.3.27-py3-none-any.whl", hash = "sha256:4ab2be254a2d5e52792d0ca793a12c35582dd09897228a6dd47885dabd5c9521"}, + {file = "furo-2023.3.27.tar.gz", hash = "sha256:b99e7867a5cc833b2b34d7230631dd6558c7a29f93071fdbb5709634bb33c5a5"}, ] [package.dependencies] @@ -695,8 +708,8 @@ files = [ name = "msgpack" version = "1.0.4" description = "MessagePack serializer" -category = "dev" -optional = false +category = "main" +optional = true python-versions = "*" files = [ {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, @@ -796,8 +809,8 @@ testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", name = "orjson" version = "3.8.4" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" files = [ {file = "orjson-3.8.4-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:94841c3418b818c8cda01ad632f0e740dad3a5bcbc38609f1fd163f4bd202ef9"}, @@ -943,6 +956,33 @@ importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "psutil" +version = "5.9.4" +description = "Cross-platform lib for process and system monitoring in Python." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, + {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, + {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, + {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, + {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, + {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, + {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, + {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + [[package]] name = "py" version = "1.11.0" @@ -1010,8 +1050,8 @@ plugins = ["importlib-metadata"] name = "pymongo" version = "4.3.3" description = "Python driver for MongoDB " -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" files = [ {file = "pymongo-4.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:74731c9e423c93cbe791f60c27030b6af6a948cef67deca079da6cd1bb583a8e"}, @@ -1103,16 +1143,22 @@ zstd = ["zstandard"] [[package]] name = "pyperf" -version = "2.5.0" +version = "2.6.0" description = "Python module to run and analyze benchmarks" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "pyperf-2.5.0-py3-none-any.whl", hash = "sha256:41633bf3e55094834a803f9be612e5658bab4df6215c324de91d93ec96f16cef"}, - {file = "pyperf-2.5.0.tar.gz", hash = "sha256:9fd9be5b57224e68b5a5b88f7126f15b6c8667573f62a0a39faf14d6fdd13909"}, + {file = "pyperf-2.6.0-py3-none-any.whl", hash = "sha256:3e95511cf0c39d68e9e55716ef1b582e7fc1949ec831ef710566b733b44aebaf"}, + {file = "pyperf-2.6.0.tar.gz", hash = "sha256:d7e367a1ec7035d7a2b25f55a5925596c00cb15851f28cffd85b05b7307232af"}, ] +[package.dependencies] +psutil = ">=5.9.0" + +[package.extras] +dev = ["importlib-metadata", "tox"] + [[package]] name = "pytest" version = "7.2.0" @@ -1202,7 +1248,7 @@ files = [ name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1213,6 +1259,13 @@ files = [ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, @@ -1495,8 +1548,8 @@ files = [ name = "tomlkit" version = "0.11.6" description = "Style preserving TOML library" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.6" files = [ {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, @@ -1580,8 +1633,8 @@ files = [ name = "ujson" version = "5.7.0" description = "Ultra fast JSON encoder and decoder for Python" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" files = [ {file = "ujson-5.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5eba5e69e4361ac3a311cf44fa71bc619361b6e0626768a494771aacd1c2f09b"}, @@ -1706,7 +1759,16 @@ files = [ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +[extras] +bson = ["pymongo"] +cbor2 = ["cbor2"] +msgpack = ["msgpack"] +orjson = ["orjson"] +pyyaml = ["PyYAML"] +tomlkit = ["tomlkit"] +ujson = ["ujson"] + [metadata] lock-version = "2.0" python-versions = ">= 3.7" -content-hash = "9c3822c11c795dc6f7cb06cf4011f64f888ff599375c1298a2c1691efb451683" +content-hash = "b7bfe9dbd804e99f0c3b674050302cc9ec8c90f63b3496d91e1de593af6ed967" diff --git a/pyproject.toml b/pyproject.toml index fc77ec31..814313ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ pytest-benchmark = "^3.2.3" hypothesis = "^6.54.5" pendulum = "^2.1.2" isort = { version = "5.10.1", python = "<4" } -black = "^23.1.0" +black = "^23.3.0" immutables = "^0.18" furo = "^2023.3.27" coverage = "^6.2" diff --git a/src/cattrs/_compat.py b/src/cattrs/_compat.py index 413bdc83..36be2f5b 100644 --- a/src/cattrs/_compat.py +++ b/src/cattrs/_compat.py @@ -252,6 +252,8 @@ def copy_with(type, args): from typing import Counter as TypingCounter from typing import ( Generic, + NotRequired, + Required, Union, _AnnotatedAlias, _GenericAlias, @@ -265,6 +267,11 @@ def is_typeddict(cls) -> bool: is_generic(cls) and (cls.__origin__.__class__ is _TypedDictMeta) ) + def get_notrequired_base(type) -> "Union[Any, Literal[NOTHING]]": + if get_origin(type) in (NotRequired, Required): + return get_args(type)[0] + return NOTHING + try: # Not present on 3.9.0, so we try carefully. from typing import _LiteralGenericAlias diff --git a/src/cattrs/converters.py b/src/cattrs/converters.py index 6c2f47fa..01708cc5 100644 --- a/src/cattrs/converters.py +++ b/src/cattrs/converters.py @@ -55,6 +55,11 @@ ) from .disambiguators import create_uniq_field_dis_func from .dispatch import MultiStrategyDispatch +from .errors import ( + IterableValidationError, + IterableValidationNote, + StructureHandlerNotFoundError, +) from .gen import ( AttributeOverride, DictStructureFn, @@ -934,7 +939,7 @@ def gen_structure_typeddict(self, cl: Any) -> Callable[[Dict], Dict]: def gen_structure_attrs_fromdict( self, cl: Type[T] ) -> Callable[[Mapping[str, Any], Any], T]: - attribs = fields(get_origin(cl) if is_generic(cl) else cl) + attribs = fields(get_origin(cl) or cl if is_generic(cl) else cl) if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs): # PEP 563 annotations - need to be resolved. resolve_types(cl) diff --git a/src/cattrs/gen/__init__.py b/src/cattrs/gen/__init__.py index f8674db4..1ea1e725 100644 --- a/src/cattrs/gen/__init__.py +++ b/src/cattrs/gen/__init__.py @@ -85,7 +85,8 @@ def make_dict_unstructure_fn( if is_generic(base) and not str(base).startswith("typing.Generic"): mapping = generate_mapping(base, mapping) break - cl = origin + if origin is not None: + cl = origin cl_name = cl.__name__ fn_name = "unstructure_" + cl_name @@ -234,7 +235,8 @@ def make_dict_structure_fn( if is_generic(cl): base = get_origin(cl) mapping = generate_mapping(cl, mapping) - cl = base + if base is not None: + cl = base for base in getattr(cl, "__orig_bases__", ()): if is_generic(base) and not str(base).startswith("typing.Generic"): diff --git a/src/cattrs/gen/_generics.py b/src/cattrs/gen/_generics.py index badd2819..a1161ea9 100644 --- a/src/cattrs/gen/_generics.py +++ b/src/cattrs/gen/_generics.py @@ -34,6 +34,8 @@ def generate_mapping(cl: Type, old_mapping: Dict[str, type] = {}) -> Dict[str, t if not hasattr(base, "__args__"): continue base_args = base.__args__ + if not hasattr(base.__origin__, "__parameters__"): + continue base_params = base.__origin__.__parameters__ for param, arg in zip(base_params, base_args): mapping[param.__name__] = arg diff --git a/src/cattrs/gen/_shared.py b/src/cattrs/gen/_shared.py index 7fc6cb26..63e21cd8 100644 --- a/src/cattrs/gen/_shared.py +++ b/src/cattrs/gen/_shared.py @@ -2,7 +2,9 @@ from typing import TYPE_CHECKING, Any, Callable, Optional -from attr import Attribute +from attr import NOTHING, Attribute, Factory + +from .._compat import is_bare_final if TYPE_CHECKING: # pragma: no cover from cattr.converters import BaseConverter @@ -24,7 +26,25 @@ def find_structure_handler( if handler == c._structure_error: handler = None elif type is not None: - handler = c._structure_func.dispatch(type) + if ( + is_bare_final(type) + and a.default is not NOTHING + and not isinstance(a.default, Factory) + ): + # This is a special case where we can use the + # type of the default to dispatch on. + type = a.default.__class__ + handler = c._structure_func.dispatch(type) + if handler == c._structure_call: + # Finals can't really be used with _structure_call, so + # we wrap it so the rest of the toolchain doesn't get + # confused. + + def handler(v, _, _h=handler): + return _h(v, type) + + else: + handler = c._structure_func.dispatch(type) else: handler = c.structure return handler diff --git a/src/cattrs/gen/typeddicts.py b/src/cattrs/gen/typeddicts.py index 52d4feec..6bbaf8cb 100644 --- a/src/cattrs/gen/typeddicts.py +++ b/src/cattrs/gen/typeddicts.py @@ -7,7 +7,13 @@ from attr import NOTHING, Attribute -from .._compat import get_origin, is_annotated, is_bare, is_generic +from .._compat import ( + get_notrequired_base, + get_origin, + is_annotated, + is_bare, + is_generic, +) from .._generics import deep_copy_with from ..errors import ClassValidationError, StructureHandlerNotFoundError from . import AttributeOverride @@ -80,6 +86,9 @@ def make_dict_unstructure_fn( break handler = None t = a.type + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb if isinstance(t, TypeVar): if t.__name__ in mapping: @@ -103,10 +112,10 @@ def make_dict_unstructure_fn( return converter._unstructure_identity for a in attrs: - print(a) attr_name = a.name override = kwargs.get(attr_name, neutral) if override.omit: + lines.append(f" res.pop('{attr_name}', None)") continue kn = attr_name if override.rename is None else override.rename attr_required = attr_name in req_keys @@ -119,6 +128,10 @@ def make_dict_unstructure_fn( handler = override.unstruct_hook else: t = a.type + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + if isinstance(t, TypeVar): if t.__name__ in mapping: t = mapping[t.__name__] @@ -252,6 +265,10 @@ def make_dict_structure_fn( if override.omit: continue t = a.type + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + if isinstance(t, TypeVar): t = mapping.get(t.__name__, t) elif is_generic(t) and not is_bare(t) and not is_annotated(t): @@ -320,7 +337,12 @@ def make_dict_structure_fn( if not attr_required: non_required.append(a) continue + t = a.type + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + if isinstance(t, TypeVar): t = mapping.get(t.__name__, t) elif is_generic(t) and not is_bare(t) and not is_annotated(t): @@ -362,6 +384,11 @@ def make_dict_structure_fn( an = a.name override = kwargs.get(an, neutral) t = a.type + + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + if isinstance(t, TypeVar): t = mapping.get(t.__name__, t) elif is_generic(t) and not is_bare(t) and not is_annotated(t): diff --git a/tests/test_typeddicts.py b/tests/test_typeddicts.py index d546d6a0..c80d13cb 100644 --- a/tests/test_typeddicts.py +++ b/tests/test_typeddicts.py @@ -7,7 +7,9 @@ from cattrs import Converter from cattrs._compat import is_generic +from cattrs.gen import override from cattrs.gen._generics import generate_mapping +from cattrs.gen.typeddicts import make_dict_structure_fn, make_dict_unstructure_fn from .typeddicts import ( generic_typeddicts, @@ -147,6 +149,59 @@ def test_generics( assert restructured == instance -@given(generic_typeddicts(total=True), booleans()) -def test_not_required() -> None: - pass +@given(simple_typeddicts(total=True, not_required=True), booleans()) +def test_not_required( + cls_and_instance: tuple[type, dict], detailed_validation: bool +) -> None: + """NotRequired[] keys are handled.""" + c = mk_converter(detailed_validation=detailed_validation) + cls, instance = cls_and_instance + + unstructured = c.unstructure(instance, unstructure_as=cls) + restructured = c.structure(unstructured, cls) + + assert restructured == instance + + +@given(simple_typeddicts(total=False, not_required=True), booleans()) +def test_required( + cls_and_instance: tuple[type, dict], detailed_validation: bool +) -> None: + """Required[] keys are handled.""" + c = mk_converter(detailed_validation=detailed_validation) + cls, instance = cls_and_instance + + unstructured = c.unstructure(instance, unstructure_as=cls) + restructured = c.structure(unstructured, cls) + + assert restructured == instance + + +@given(simple_typeddicts(min_attrs=1, total=True), booleans()) +def test_omit(cls_and_instance: tuple[type, dict], detailed_validation: bool) -> None: + """`override(omit=True)` works.""" + c = mk_converter(detailed_validation=detailed_validation) + + cls, instance = cls_and_instance + key = next(iter(get_annot(cls))) + c.register_unstructure_hook( + cls, make_dict_unstructure_fn(cls, c, **{key: override(omit=True)}) + ) + + unstructured = c.unstructure(instance, unstructure_as=cls) + + assert key not in unstructured + + unstructured[key] = c.unstructure(instance[key]) + restructured = c.structure(unstructured, cls) + + assert restructured == instance + + c.register_structure_hook( + cls, make_dict_structure_fn(cls, c, **{key: override(omit=True)}) + ) + del unstructured[key] + del instance[key] + restructured = c.structure(unstructured, cls) + + assert restructured == instance diff --git a/tests/typeddicts.py b/tests/typeddicts.py index 7ce14ca9..f9a421f9 100644 --- a/tests/typeddicts.py +++ b/tests/typeddicts.py @@ -42,36 +42,61 @@ def int_attributes( return int, integers() | just(NOTHING), text(ascii_lowercase) +@composite def datetime_attributes( - total: bool = True, -) -> SearchStrategy[tuple[datetime, SearchStrategy, SearchStrategy]]: + draw: DrawFn, total: bool = True, not_required: bool = False +) -> tuple[datetime, SearchStrategy, SearchStrategy]: success_strat = datetimes().map(lambda dt: dt.replace(microsecond=0)) - return just( - ( - datetime, - success_strat if total else success_strat | just(NOTHING), - text(ascii_lowercase), - ) - ) + type = datetime + strat = success_strat if total else success_strat | just(NOTHING) + if not_required and draw(booleans()): + if total: + type = NotRequired[type] + strat = success_strat | just(NOTHING) + else: + type = Required[type] + strat = success_strat + return (type, strat, text(ascii_lowercase)) @composite def list_of_int_attributes( - draw: DrawFn, total: bool = True + draw: DrawFn, total: bool = True, not_required: bool = False ) -> tuple[List[int], SearchStrategy, SearchStrategy]: if total: - return List[int], lists(integers()), text(ascii_lowercase).map(lambda v: [v]) + if not_required and draw(booleans()): + return ( + NotRequired[List[int]], + lists(integers()) | just(NOTHING), + text(ascii_lowercase).map(lambda v: [v]), + ) + else: + return ( + List[int], + lists(integers()), + text(ascii_lowercase).map(lambda v: [v]), + ) else: - return ( - List[int], - lists(integers()) | just(NOTHING), - text(ascii_lowercase).map(lambda v: [v]), - ) + if not_required and draw(booleans()): + return ( + Required[List[int]], + lists(integers()), + text(ascii_lowercase).map(lambda v: [v]), + ) + else: + return ( + List[int], + lists(integers()) | just(NOTHING), + text(ascii_lowercase).map(lambda v: [v]), + ) @composite def simple_typeddicts( - draw: DrawFn, total: Optional[bool] = None, not_required: bool = False + draw: DrawFn, + total: Optional[bool] = None, + not_required: bool = False, + min_attrs: int = 0, ) -> tuple[TypedDictType, dict]: """Generate simple typed dicts. @@ -83,8 +108,9 @@ def simple_typeddicts( attrs = draw( lists( int_attributes(total, not_required) - | list_of_int_attributes(total) - | datetime_attributes(total) + | list_of_int_attributes(total, not_required) + | datetime_attributes(total, not_required), + min_size=min_attrs, ) ) @@ -124,7 +150,7 @@ def simple_typeddicts_with_extra_keys( @composite def generic_typeddicts( - draw: DrawFn, total: Optional[bool] = None, include_not_required: bool = False + draw: DrawFn, total: Optional[bool] = None ) -> tuple[TypedDictType, dict]: """Generate generic typed dicts. From 192d19a3e79dd29988f4f7334543706be1cb3092 Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Mon, 17 Apr 2023 03:10:33 +0200 Subject: [PATCH 4/8] Tests for overrides --- src/cattrs/gen/typeddicts.py | 35 ++++++++++++++---- src/cattrs/v.py | 6 +++ tests/test_typeddicts.py | 52 +++++++++++++++++++++++++- tests/test_v.py | 72 ++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 10 deletions(-) diff --git a/src/cattrs/gen/typeddicts.py b/src/cattrs/gen/typeddicts.py index 6bbaf8cb..082d1a29 100644 --- a/src/cattrs/gen/typeddicts.py +++ b/src/cattrs/gen/typeddicts.py @@ -15,7 +15,12 @@ is_generic, ) from .._generics import deep_copy_with -from ..errors import ClassValidationError, StructureHandlerNotFoundError +from ..errors import ( + AttributeValidationNote, + ClassValidationError, + ForbiddenExtraKeysError, + StructureHandlerNotFoundError, +) from . import AttributeOverride from ._consts import already_generating, neutral from ._generics import generate_mapping @@ -117,6 +122,10 @@ def make_dict_unstructure_fn( if override.omit: lines.append(f" res.pop('{attr_name}', None)") continue + if override.rename is not None: + # We also need to pop when renaming, since we're copying + # the original. + lines.append(f" res.pop('{attr_name}', None)") kn = attr_name if override.rename is None else override.rename attr_required = attr_name in req_keys @@ -154,10 +163,13 @@ def make_dict_unstructure_fn( globs[unstruct_handler_name] = handler internal_arg_parts[unstruct_handler_name] = handler invoke = f"{unstruct_handler_name}(instance['{attr_name}'])" - else: + elif override.rename is None: # We're not doing anything to this attribute, so # it'll already be present in the input dict. continue + else: + # Probably renamed, we just fetch it. + invoke = f"instance['{attr_name}']" if attr_required: # No default or no override. @@ -258,6 +270,7 @@ def make_dict_structure_fn( if _cattrs_detailed_validation: lines.append(" errors = []") internal_arg_parts["__c_cve"] = ClassValidationError + internal_arg_parts["__c_avn"] = AttributeValidationNote for a in attrs: an = a.name attr_required = an in req_keys @@ -294,23 +307,25 @@ def make_dict_structure_fn( i = f"{i} " lines.append(f"{i}try:") i = f"{i} " + type_name = f"__c_type_{an}" + internal_arg_parts[type_name] = t if handler: if handler == converter._structure_call: internal_arg_parts[struct_handler_name] = t lines.append(f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'])") else: - type_name = f"__c_type_{an}" - internal_arg_parts[type_name] = t lines.append( f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'], {type_name})" ) else: lines.append(f"{i}res['{an}'] = o['{kn}']") + if override.rename is not None: + lines.append(f"{i}del res['{kn}']") i = i[:-2] lines.append(f"{i}except Exception as e:") i = f"{i} " lines.append( - f"{i}e.__notes__ = getattr(e, '__notes__', []) + [\"Structuring class {cl.__qualname__} @ attribute {an}\"]" + f'{i}e.__notes__ = getattr(e, \'__notes__\', []) + [__c_avn("Structuring typeddict {cl.__qualname__} @ attribute {an}", "{an}", __c_type_{an})]' ) lines.append(f"{i}errors.append(e)") @@ -365,18 +380,20 @@ def make_dict_structure_fn( if handler == converter._structure_call: internal_arg_parts[struct_handler_name] = t invocation_line = ( - f" res['{kn}'] = {struct_handler_name}(o['{kn}'])" + f" res['{an}'] = {struct_handler_name}(o['{kn}'])" ) else: type_name = f"__c_type_{an}" internal_arg_parts[type_name] = t invocation_line = ( - f" res['{kn}'] = {struct_handler_name}(o['{kn}'], {type_name})" + f" res['{an}'] = {struct_handler_name}(o['{kn}'], {type_name})" ) else: - invocation_line = f" res['{kn}'] = o['{kn}']" + invocation_line = f" res['{an}'] = o['{kn}']" lines.append(invocation_line) + if override.rename is not None: + lines.append(f" del res['{override.rename}']") # The second loop is for optional args. if non_required: @@ -423,6 +440,8 @@ def make_dict_structure_fn( ) else: post_lines.append(f" res['{ian}'] = o['{kn}']") + if override.rename is not None: + lines.append(f" res.pop('{override.rename}', None)") # if _cattrs_forbid_extra_keys: # post_lines += [ diff --git a/src/cattrs/v.py b/src/cattrs/v.py index 5be3b5c1..3e785640 100644 --- a/src/cattrs/v.py +++ b/src/cattrs/v.py @@ -44,6 +44,12 @@ def format_exception(exc: BaseException, type: Union[Type, None]) -> str: ): # This was supposed to be a mapping (and have .items()) but it something else. res = "expected a mapping" + elif isinstance(exc, AttributeError) and exc.args[0].endswith( + "object has no attribute 'copy'" + ): + # This was supposed to be a mapping (and have .copy()) but it something else. + # Used for TypedDicts. + res = "expected a mapping" else: res = f"unknown error ({exc})" diff --git a/tests/test_typeddicts.py b/tests/test_typeddicts.py index c80d13cb..e489608f 100644 --- a/tests/test_typeddicts.py +++ b/tests/test_typeddicts.py @@ -185,7 +185,13 @@ def test_omit(cls_and_instance: tuple[type, dict], detailed_validation: bool) -> cls, instance = cls_and_instance key = next(iter(get_annot(cls))) c.register_unstructure_hook( - cls, make_dict_unstructure_fn(cls, c, **{key: override(omit=True)}) + cls, + make_dict_unstructure_fn( + cls, + c, + _cattrs_detailed_validation=detailed_validation, + **{key: override(omit=True)} + ), ) unstructured = c.unstructure(instance, unstructure_as=cls) @@ -198,10 +204,52 @@ def test_omit(cls_and_instance: tuple[type, dict], detailed_validation: bool) -> assert restructured == instance c.register_structure_hook( - cls, make_dict_structure_fn(cls, c, **{key: override(omit=True)}) + cls, + make_dict_structure_fn( + cls, + c, + _cattrs_detailed_validation=detailed_validation, + **{key: override(omit=True)} + ), ) del unstructured[key] del instance[key] restructured = c.structure(unstructured, cls) assert restructured == instance + + +@given(simple_typeddicts(min_attrs=1, total=True), booleans()) +def test_rename(cls_and_instance: tuple[type, dict], detailed_validation: bool) -> None: + """`override(rename=...)` works.""" + c = mk_converter(detailed_validation=detailed_validation) + + cls, instance = cls_and_instance + key = next(iter(get_annot(cls))) + c.register_unstructure_hook( + cls, + make_dict_unstructure_fn( + cls, + c, + _cattrs_detailed_validation=detailed_validation, + **{key: override(rename="renamed")} + ), + ) + + unstructured = c.unstructure(instance, unstructure_as=cls) + + assert key not in unstructured + assert "renamed" in unstructured + + c.register_structure_hook( + cls, + make_dict_structure_fn( + cls, + c, + _cattrs_detailed_validation=detailed_validation, + **{key: override(rename="renamed")} + ), + ) + restructured = c.structure(unstructured, cls) + + assert restructured == instance diff --git a/tests/test_v.py b/tests/test_v.py index 7659115e..e0bd77d1 100644 --- a/tests/test_v.py +++ b/tests/test_v.py @@ -7,6 +7,7 @@ Optional, Sequence, Tuple, + TypedDict, ) from attrs import Factory, define @@ -225,3 +226,74 @@ class C: "no key @ $.a", "invalid value for type, expected int @ $.b", ] + + +def test_typeddict_attribute_errors(c: Converter) -> None: + """TypedDict errors are correctly generated.""" + + class C(TypedDict): + a: int + b: int + + try: + c.structure({}, C) + except Exception as exc: + assert transform_error(exc) == [ + "required field missing @ $.a", + "required field missing @ $.b", + ] + + try: + c.structure({"b": 1}, C) + except Exception as exc: + assert transform_error(exc) == ["required field missing @ $.a"] + + try: + c.structure({"a": 1, "b": "str"}, C) + except Exception as exc: + assert transform_error(exc) == ["invalid value for type, expected int @ $.b"] + + class D(TypedDict): + c: C + + try: + c.structure({}, D) + except Exception as exc: + assert transform_error(exc) == ["required field missing @ $.c"] + + try: + c.structure({"c": {}}, D) + except Exception as exc: + assert transform_error(exc) == [ + "required field missing @ $.c.a", + "required field missing @ $.c.b", + ] + + try: + c.structure({"c": 1}, D) + except Exception as exc: + assert transform_error(exc) == ["expected a mapping @ $.c"] + + try: + c.structure({"c": {"a": "str"}}, D) + except Exception as exc: + assert transform_error(exc) == [ + "invalid value for type, expected int @ $.c.a", + "required field missing @ $.c.b", + ] + + class E(TypedDict): + a: Optional[int] + + with raises(Exception) as exc: + c.structure({"a": "str"}, E) + + # Complicated due to various Python versions. + tn = ( + Optional[int].__name__ + if hasattr(Optional[int], "__name__") + else repr(Optional[int]) + ) + assert transform_error(exc.value) == [ + f"invalid value for type, expected {tn} @ $.a" + ] From b52cd84c795492c9af6da9406a2f9de8b82e92eb Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Sun, 14 May 2023 02:39:44 +0200 Subject: [PATCH 5/8] forbid_extra_keys for typed dicts --- src/cattrs/gen/typeddicts.py | 31 ++++++++++--------- tests/test_typeddicts.py | 59 +++++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 19 deletions(-) diff --git a/src/cattrs/gen/typeddicts.py b/src/cattrs/gen/typeddicts.py index 082d1a29..f9f27b00 100644 --- a/src/cattrs/gen/typeddicts.py +++ b/src/cattrs/gen/typeddicts.py @@ -209,6 +209,7 @@ def make_dict_unstructure_fn( def make_dict_structure_fn( cl: Any, converter: BaseConverter, + _cattrs_forbid_extra_keys: bool = False, _cattrs_use_linecache: bool = True, _cattrs_detailed_validation: bool = True, **kwargs: AttributeOverride, @@ -261,9 +262,9 @@ def make_dict_structure_fn( attrs = adapted_fields(cl) allowed_fields = set() - # if _cattrs_forbid_extra_keys: - # globs["__c_a"] = allowed_fields - # globs["__c_feke"] = ForbiddenExtraKeysError + if _cattrs_forbid_extra_keys: + globs["__c_a"] = allowed_fields + globs["__c_feke"] = ForbiddenExtraKeysError lines.append(" res = o.copy()") @@ -329,12 +330,12 @@ def make_dict_structure_fn( ) lines.append(f"{i}errors.append(e)") - # if _cattrs_forbid_extra_keys: - # post_lines += [ - # " unknown_fields = set(o.keys()) - __c_a", - # " if unknown_fields:", - # " errors.append(__c_feke('', __cl, unknown_fields))", - # ] + if _cattrs_forbid_extra_keys: + post_lines += [ + " unknown_fields = o.keys() - __c_a", + " if unknown_fields:", + " errors.append(__c_feke('', __cl, unknown_fields))", + ] post_lines.append( f" if errors: raise __c_cve('While structuring ' + {cl.__name__!r}, errors, __cl)" @@ -443,12 +444,12 @@ def make_dict_structure_fn( if override.rename is not None: lines.append(f" res.pop('{override.rename}', None)") - # if _cattrs_forbid_extra_keys: - # post_lines += [ - # " unknown_fields = set(o.keys()) - __c_a", - # " if unknown_fields:", - # " raise __c_feke('', __cl, unknown_fields)", - # ] + if _cattrs_forbid_extra_keys: + post_lines += [ + " unknown_fields = o.keys() - __c_a", + " if unknown_fields:", + " raise __c_feke('', __cl, unknown_fields)", + ] # At the end, we create the function header. internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) diff --git a/tests/test_typeddicts.py b/tests/test_typeddicts.py index e489608f..f0a2b8c2 100644 --- a/tests/test_typeddicts.py +++ b/tests/test_typeddicts.py @@ -4,9 +4,11 @@ from hypothesis import assume, given from hypothesis.strategies import booleans +from pytest import raises from cattrs import Converter from cattrs._compat import is_generic +from cattrs.errors import ClassValidationError, ForbiddenExtraKeysError from cattrs.gen import override from cattrs.gen._generics import generate_mapping from cattrs.gen.typeddicts import make_dict_structure_fn, make_dict_unstructure_fn @@ -190,7 +192,7 @@ def test_omit(cls_and_instance: tuple[type, dict], detailed_validation: bool) -> cls, c, _cattrs_detailed_validation=detailed_validation, - **{key: override(omit=True)} + **{key: override(omit=True)}, ), ) @@ -209,7 +211,7 @@ def test_omit(cls_and_instance: tuple[type, dict], detailed_validation: bool) -> cls, c, _cattrs_detailed_validation=detailed_validation, - **{key: override(omit=True)} + **{key: override(omit=True)}, ), ) del unstructured[key] @@ -232,7 +234,7 @@ def test_rename(cls_and_instance: tuple[type, dict], detailed_validation: bool) cls, c, _cattrs_detailed_validation=detailed_validation, - **{key: override(rename="renamed")} + **{key: override(rename="renamed")}, ), ) @@ -247,9 +249,58 @@ def test_rename(cls_and_instance: tuple[type, dict], detailed_validation: bool) cls, c, _cattrs_detailed_validation=detailed_validation, - **{key: override(rename="renamed")} + **{key: override(rename="renamed")}, ), ) restructured = c.structure(unstructured, cls) assert restructured == instance + + +@given(simple_typeddicts(total=True), booleans()) +def test_forbid_extra_keys( + cls_and_instance: tuple[type, dict], detailed_validation: bool +) -> None: + """Extra keys can be forbidden.""" + c = mk_converter(detailed_validation) + + cls, instance = cls_and_instance + + c.register_structure_hook( + cls, + make_dict_structure_fn( + cls, + c, + _cattrs_detailed_validation=detailed_validation, + _cattrs_forbid_extra_keys=True, + ), + ) + + unstructured = c.unstructure(instance, unstructure_as=cls) + + structured = c.structure(unstructured, cls) + assert structured == instance + + # An extra key will trigger the appropriate error. + unstructured["test"] = 1 + + if not detailed_validation: + with raises(ForbiddenExtraKeysError): + c.structure(unstructured, cls) + else: + with raises(ClassValidationError) as ctx: + c.structure(unstructured, cls) + + assert repr(ctx.value) == repr( + ClassValidationError( + f"While structuring {cls.__name__}", + [ + ForbiddenExtraKeysError( + f"Extra fields in constructor for {cls.__name__}: test", + cls, + {"test"}, + ) + ], + cls, + ) + ) From 3fb4f228f743494aa8674193c46e48af51c5f98f Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Fri, 19 May 2023 00:53:38 +0200 Subject: [PATCH 6/8] Compatibility fixes --- poetry.lock | 661 ++++++++++++++++++++--------------- pyproject.toml | 2 +- src/cattrs/_compat.py | 91 ++++- src/cattrs/gen/__init__.py | 6 +- src/cattrs/gen/typeddicts.py | 117 ++++++- tests/test_gen_dict.py | 6 +- tests/test_generics.py | 3 +- tests/test_typeddicts.py | 59 +++- tests/test_v.py | 3 +- tests/typeddicts.py | 36 +- 10 files changed, 638 insertions(+), 346 deletions(-) diff --git a/poetry.lock b/poetry.lock index 316d4e38..4dcd2324 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "alabaster" @@ -14,48 +14,51 @@ files = [ [[package]] name = "attrs" -version = "22.2.0" +version = "23.1.0" description = "Classes Without Boilerplate" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, ] +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + [package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] [[package]] name = "babel" -version = "2.11.0" +version = "2.12.1" description = "Internationalization utilities" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, - {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, + {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, + {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, ] [package.dependencies] -pytz = ">=2015.7" +pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [[package]] name = "beautifulsoup4" -version = "4.11.1" +version = "4.12.2" description = "Screen-scraping library" category = "dev" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, - {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, ] [package.dependencies] @@ -168,31 +171,101 @@ test = ["pytest", "pytest-cov"] [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." category = "dev" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, ] [[package]] name = "charset-normalizer" -version = "2.0.12" +version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "dev" optional = false -python-versions = ">=3.5.0" -files = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, ] -[package.extras] -unicode-backport = ["unicodedata2"] - [[package]] name = "click" version = "8.1.3" @@ -329,14 +402,14 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.0" +version = "1.1.1" description = "Backport of PEP 654 (exception groups)" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, - {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, ] [package.extras] @@ -344,19 +417,19 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.9.0" +version = "3.12.0" description = "A platform independent file lock." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, - {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, + {file = "filelock-3.12.0-py3-none-any.whl", hash = "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9"}, + {file = "filelock-3.12.0.tar.gz", hash = "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718"}, ] [package.extras] -docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] [[package]] name = "flake8" @@ -395,14 +468,14 @@ sphinx-basic-ng = "*" [[package]] name = "hypothesis" -version = "6.62.0" +version = "6.75.3" description = "A library for property-based testing" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "hypothesis-6.62.0-py3-none-any.whl", hash = "sha256:e250da77878460f74b53039493a7a18d6fc137b0b77791b382b6a0f4ada9144e"}, - {file = "hypothesis-6.62.0.tar.gz", hash = "sha256:76f1141e8237f6dd0780a171bec5d6aec873208ccc27b5f9753d4cccd8904272"}, + {file = "hypothesis-6.75.3-py3-none-any.whl", hash = "sha256:a12bf34c29bd22757d20edf93f95805978ed0ffb8d0b22dbadc890a79dc9baa8"}, + {file = "hypothesis-6.75.3.tar.gz", hash = "sha256:15cdadb80a7ac59087581624d266a4fb585b5cce9b7f88f506c481a9f0e583f6"}, ] [package.dependencies] @@ -411,7 +484,7 @@ exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "importlib-metadata (>=3.6)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=1.0)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2022.7)"] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "importlib-metadata (>=3.6)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.16.0)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2023.3)"] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] dateutil = ["python-dateutil (>=1.4)"] @@ -419,12 +492,12 @@ django = ["django (>=3.2)"] dpcontracts = ["dpcontracts (>=0.4)"] ghostwriter = ["black (>=19.10b0)"] lark = ["lark (>=0.10.1)"] -numpy = ["numpy (>=1.9.0)"] -pandas = ["pandas (>=1.0)"] +numpy = ["numpy (>=1.16.0)"] +pandas = ["pandas (>=1.1)"] pytest = ["pytest (>=4.6)"] pytz = ["pytz (>=2014.1)"] redis = ["redis (>=3.0.0)"] -zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.7)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2023.3)"] [[package]] name = "idna" @@ -517,14 +590,14 @@ test = ["flake8 (>=3.8.4,<3.9.0)", "mypy (==0.942)", "pycodestyle (>=2.6.0,<2.7. [[package]] name = "importlib-metadata" -version = "6.0.0" +version = "6.6.0" description = "Read metadata from Python packages" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, - {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, + {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"}, + {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"}, ] [package.dependencies] @@ -586,14 +659,14 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markdown-it-py" -version = "2.1.0" +version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, - {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, + {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, + {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, ] [package.dependencies] @@ -601,10 +674,10 @@ mdurl = ">=0.1,<1.0" typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] -code-style = ["pre-commit (==2.6)"] -compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] -linkify = ["linkify-it-py (>=1.0,<2.0)"] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] @@ -612,52 +685,62 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.1" +version = "2.1.2" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, + {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, ] [[package]] @@ -674,14 +757,14 @@ files = [ [[package]] name = "mdit-py-plugins" -version = "0.3.3" +version = "0.3.5" description = "Collection of plugins for markdown-it-py" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mdit-py-plugins-0.3.3.tar.gz", hash = "sha256:5cfd7e7ac582a594e23ba6546a2f406e94e42eb33ae596d0734781261c251260"}, - {file = "mdit_py_plugins-0.3.3-py3-none-any.whl", hash = "sha256:36d08a29def19ec43acdcd8ba471d3ebab132e7879d442760d963f19913e04b9"}, + {file = "mdit-py-plugins-0.3.5.tar.gz", hash = "sha256:eee0adc7195e5827e17e02d2a258a2ba159944a0748f59c5099a4a27f78fcf6a"}, + {file = "mdit_py_plugins-0.3.5-py3-none-any.whl", hash = "sha256:ca9a0714ea59a24b2b044a1831f48d817dd0c817e84339f20e7889f392d77c4e"}, ] [package.dependencies] @@ -706,76 +789,87 @@ files = [ [[package]] name = "msgpack" -version = "1.0.4" +version = "1.0.5" description = "MessagePack serializer" category = "main" optional = true python-versions = "*" files = [ - {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, - {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"}, - {file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"}, - {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6"}, - {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa"}, - {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6"}, - {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba"}, - {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e"}, - {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db"}, - {file = "msgpack-1.0.4-cp310-cp310-win32.whl", hash = "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef"}, - {file = "msgpack-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075"}, - {file = "msgpack-1.0.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52"}, - {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9"}, - {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9"}, - {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08"}, - {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8"}, - {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6"}, - {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae"}, - {file = "msgpack-1.0.4-cp36-cp36m-win32.whl", hash = "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6"}, - {file = "msgpack-1.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661"}, - {file = "msgpack-1.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c"}, - {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0"}, - {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227"}, - {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff"}, - {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd"}, - {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e"}, - {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236"}, - {file = "msgpack-1.0.4-cp37-cp37m-win32.whl", hash = "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44"}, - {file = "msgpack-1.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1"}, - {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d"}, - {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab"}, - {file = "msgpack-1.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb"}, - {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9"}, - {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e"}, - {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1"}, - {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e"}, - {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43"}, - {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243"}, - {file = "msgpack-1.0.4-cp38-cp38-win32.whl", hash = "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2"}, - {file = "msgpack-1.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6"}, - {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae"}, - {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55"}, - {file = "msgpack-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da"}, - {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f"}, - {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92"}, - {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f"}, - {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624"}, - {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8"}, - {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae"}, - {file = "msgpack-1.0.4-cp39-cp39-win32.whl", hash = "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c"}, - {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"}, - {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, + {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, + {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, + {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, + {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, + {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, + {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, + {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, + {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, + {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, + {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, + {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, + {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, + {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, + {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, + {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, ] [[package]] name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] @@ -807,80 +901,82 @@ testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", [[package]] name = "orjson" -version = "3.8.4" +version = "3.8.12" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" category = "main" optional = true python-versions = ">=3.7" files = [ - {file = "orjson-3.8.4-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:94841c3418b818c8cda01ad632f0e740dad3a5bcbc38609f1fd163f4bd202ef9"}, - {file = "orjson-3.8.4-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:ffb9367d63a4c71cd0432fc5a4e295b0e89b061e90be9f5ff3ffca301ba2dd84"}, - {file = "orjson-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74c25f71a41a3a213bee02f496abbc9ebc7cb0c8d1e5c1a5ab77c87b427b3f07"}, - {file = "orjson-3.8.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:30294eeb7417eca85572b64278f5c4fdd72049725d7d23da5042a61c1a2298c9"}, - {file = "orjson-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36257f254635da390a9d689214c4cec9ec4ea224c5e2bcdfe97d15a384695be5"}, - {file = "orjson-3.8.4-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:170a041d0d967e348ab086f1c9765846658cb0e807abe98e8320f665b1c67e9b"}, - {file = "orjson-3.8.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:38df475523ca0f3f88b871821a9fb1a1437dad7c637847e0e7378024c7654473"}, - {file = "orjson-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:54ade013afcda2f32f98227f0eb3dba4267c6efe7a8abd93bfec6d7af2546370"}, - {file = "orjson-3.8.4-cp310-none-win_amd64.whl", hash = "sha256:b0a8273ebcf363042e7847faaf67f92178aceb3476e374da5432f9e6772c7a80"}, - {file = "orjson-3.8.4-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:4c3c3571118371a5b88a89a0a157b4e98827d3a540c3f683c59d80e71f66195d"}, - {file = "orjson-3.8.4-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:86f71f2abc24b79e74a13f8d4eebb7d03cad43d6a46e2cad6fd5891423f84773"}, - {file = "orjson-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bff66b99ce8ba3d3b39ce10f27a848b3b68d80f64d77b8118d4648fafd2f964"}, - {file = "orjson-3.8.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49c59a728510a6ef20de59f6262d1415108b323996634d263e4d0d613994fc13"}, - {file = "orjson-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27225338f24a6906d254c4b8b3849135af758af55ae1f43e5ccbebcf2097628a"}, - {file = "orjson-3.8.4-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:0c42fc0ed3f12bc7d0fd61a26899a8ac32deb6622289110b6d7f4d60d47dcff3"}, - {file = "orjson-3.8.4-cp311-none-win_amd64.whl", hash = "sha256:ad6d533ab9ef8b141d6749e7591ee3a3195037c217818e3b3351df1468efc012"}, - {file = "orjson-3.8.4-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:654e219ace2c942f9d8f3a69935991f8c88efb4a85b074ba917960c800a1e5c8"}, - {file = "orjson-3.8.4-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:0b57cc7a94b133140c85c3873d22e7a1f8e0d2489619a7e2e8640f9a9edfbc29"}, - {file = "orjson-3.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:447a4c95f849872ac9af137153e8748b3b35d199c03a3bc618da6a3a23681f96"}, - {file = "orjson-3.8.4-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5d47002a88f60a35901925d4085a313647229950d2818044520eb0e30fc9447b"}, - {file = "orjson-3.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e26422430227e2a8a6d07daee5390fb6510ce67f2b45a1016595e0ca8fb9515b"}, - {file = "orjson-3.8.4-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:77034415ceb563d38b5aa9713b8945faed7a5ddf5553a5e0831cee362b81e0b2"}, - {file = "orjson-3.8.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ecbd6548232ee4d533effcd4dbf5cfc547738ce1791f6204e949f1214c0a0"}, - {file = "orjson-3.8.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b68f2f7f61b2f85623e0cd02eea04ab358cba9a01370fa8f2e96f1b2b83705b7"}, - {file = "orjson-3.8.4-cp37-none-win_amd64.whl", hash = "sha256:8b18925638936104e1c2bdc555b508733ef83c4c9ac96c5ec6666af6df12c22d"}, - {file = "orjson-3.8.4-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:c834eed62fd184d8c0a707f7d1dbaf57815c4865091bfc220182c83321c773f5"}, - {file = "orjson-3.8.4-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6b07c1d76db95706e78e904f2329796d0c26ffed5eaea7a1be753ef7032c30dc"}, - {file = "orjson-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cb431a8168510458fa9d3a1a3ed8f56909187db953138080e8c8ef44484adde"}, - {file = "orjson-3.8.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:64d60e21096de30101348de25f20c516289365c056d0987ec76c74f5073af122"}, - {file = "orjson-3.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceabb527e0641ff23d5ed89f3d9c22f3df2cbe967547d28ea7fdc09c3de1168d"}, - {file = "orjson-3.8.4-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:16a00f10321b4afacd8ecbe239e4ac8c6a77eb442caeffb638c9a49a653dfa2c"}, - {file = "orjson-3.8.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e9c0fb49362e79e3db158b8d307ff53adb4a5bd2706e1a4372393a4d661000fb"}, - {file = "orjson-3.8.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7f72c7a810977b94d0411a5edf3b0eab7bc5763089ad0a0d639e49f651a0d91"}, - {file = "orjson-3.8.4-cp38-none-win_amd64.whl", hash = "sha256:2e6b088d6a1700fad0ce18715ec309bb7efc8ec6275413e1495261a3bbfbc17a"}, - {file = "orjson-3.8.4-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:5d618f30784321361927a65a6da484ef6da218f62c594987bd4b79f97df55036"}, - {file = "orjson-3.8.4-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:42aee49489de6aa3e9b3280c9a124d2f4a984be7a714cd8b13b27634af817521"}, - {file = "orjson-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31f919b370159b3d0c3f38844d66545a4520bc2652c32b9127d02e94f107247e"}, - {file = "orjson-3.8.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5fd174091a24a5ded94eaa141b1c989ce9dd6a7af8eadd525ef93dc5849cb48e"}, - {file = "orjson-3.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e030e8e50e908a7f8ba02b69c5692e30d9687ad184f0b75bfd8cd57df695093a"}, - {file = "orjson-3.8.4-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:22b4c6066c1906b8d2a88cec8e8dc138ba2e08b17971419672734ca3ddab1995"}, - {file = "orjson-3.8.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:90ea42189d81dea219dfda58374e2d9f07e1d17052fa33ade07441e5e50d62d2"}, - {file = "orjson-3.8.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53cc5be7c5c60d7583d71144bdb77171183f015b755ad5ab4a248fc03e61dad7"}, - {file = "orjson-3.8.4-cp39-none-win_amd64.whl", hash = "sha256:c2664d39fa599beb70afe8aa92c7ca905207a0b0aeb7597c038cb42394bd1a0e"}, - {file = "orjson-3.8.4.tar.gz", hash = "sha256:9dcb8d788254936de2791d6d2c29bc41a3dc2c9d9cb4f01c06c0e6d424844593"}, + {file = "orjson-3.8.12-cp310-cp310-macosx_11_0_x86_64.macosx_11_0_arm64.macosx_11_0_universal2.whl", hash = "sha256:c84046e890e13a119404a83f2e09e622509ed4692846ff94c4ca03654fbc7fb5"}, + {file = "orjson-3.8.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29706dd8189835bcf1781faed286e99ae54fd6165437d364dfdbf0276bf39b19"}, + {file = "orjson-3.8.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4e22b0aa70c963ac01fcd620de15be21a5027711b0e5d4b96debcdeea43e3ae"}, + {file = "orjson-3.8.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d1acf52d3a4b9384af09a5c2658c3a7a472a4d62a0ad1fe2c8fab8ef460c9b4"}, + {file = "orjson-3.8.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a72b50719bdd6bb0acfca3d4d1c841aa4b191f3ff37268e7aba04e5d6be44ccd"}, + {file = "orjson-3.8.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83e8c740a718fa6d511a82e463adc7ab17631c6eea81a716b723e127a9c51d57"}, + {file = "orjson-3.8.12-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebb03e4c7648f7bb299872002a6120082da018f41ba7a9ebf4ceae8d765443d2"}, + {file = "orjson-3.8.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:44f7bb4c995652106276442de1147c9993716d1e2d79b7fd435afa154ff236b9"}, + {file = "orjson-3.8.12-cp310-none-win_amd64.whl", hash = "sha256:06e528f9a84fbb4000fd0eee573b5db543ee70ae586fdbc53e740b0ac981701c"}, + {file = "orjson-3.8.12-cp311-cp311-macosx_11_0_x86_64.macosx_11_0_arm64.macosx_11_0_universal2.whl", hash = "sha256:9a6c1594d5a9ff56e5babc4a87ac372af38d37adef9e06744e9f158431e33f43"}, + {file = "orjson-3.8.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6390ce0bce24c107fc275736aa8a4f768ef7eb5df935d7dca0cc99815eb5d99"}, + {file = "orjson-3.8.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:efb3a10030462a22c731682434df5c137a67632a8339f821cd501920b169007e"}, + {file = "orjson-3.8.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e405d54c84c30d9b1c918c290bcf4ef484a45c69d5583a95db81ffffba40b44"}, + {file = "orjson-3.8.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd6fbd1413559572e81b5ac64c45388147c3ba85cc3df2eaa11002945e0dbd1f"}, + {file = "orjson-3.8.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f480ae7b84369b1860d8867f0baf8d885fede400fda390ce088bfa8edf97ffdc"}, + {file = "orjson-3.8.12-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:355055e0977c43b0e5325b9312b7208c696fe20cd54eed1d6fc80b0a4d6721f5"}, + {file = "orjson-3.8.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d937503e4dfba5edc8d5e0426d3cc97ed55716e93212b2e12a198664487b9965"}, + {file = "orjson-3.8.12-cp311-none-win_amd64.whl", hash = "sha256:eb16e0195febd24b44f4db1ab3be85ecf6038f92fd511370cebc004b3d422294"}, + {file = "orjson-3.8.12-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:dc27a8ec13f28e92dc1ea89bf1232d77e7d3ebfd5c1ccf4f3729a70561cb63bd"}, + {file = "orjson-3.8.12-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77710774faed337ac4ad919dadc5f3b655b0cd40518e5386e6f1f116de9c6c25"}, + {file = "orjson-3.8.12-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7e549468867991f6f9cfbd9c5bbc977330173bd8f6ceb79973bbd4634e13e1b9"}, + {file = "orjson-3.8.12-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96fb1eb82b578eb6c0e53e3cf950839fe98ea210626f87c8204bd4fc2cc6ba02"}, + {file = "orjson-3.8.12-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d153b228b6e24f8bccf732a51e01e8e938eef59efed9030c5c257778fbe0804"}, + {file = "orjson-3.8.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:becbd5af6d035a7ec2ee3239d4700929d52d8517806b97dd04efcc37289403f7"}, + {file = "orjson-3.8.12-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d63f524048825e05950db3b6998c756d5377a5e8c469b2e3bdb9f3217523d74"}, + {file = "orjson-3.8.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ec4f0130d9a27cb400423e09e0f9e46480e9e977f05fdcf663a7a2c68735513e"}, + {file = "orjson-3.8.12-cp37-none-win_amd64.whl", hash = "sha256:6f1b01f641f5e87168b819ac1cbd81aa6278e7572c326f3d27e92dea442a2c0d"}, + {file = "orjson-3.8.12-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:062e67108c218fdb9475edd5272b1629c05b56c66416fa915de5656adde30e73"}, + {file = "orjson-3.8.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ba645c92801417933fa74448622ba614a275ea82df05e888095c7742d913bb4"}, + {file = "orjson-3.8.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7d50d9b1ae409ea15534365fec0ce8a5a5f7dc94aa790aacfb8cfec87ab51aa4"}, + {file = "orjson-3.8.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f00038bf5d07439d13c0c2c5cd6ad48eb86df7dbd7a484013ce6a113c421b14"}, + {file = "orjson-3.8.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:397670665f94cf5cff779054781d80395084ba97191d82f7b3a86f0a20e6102b"}, + {file = "orjson-3.8.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f568205519bb0197ca91915c5da6058cfbb59993e557b02dfc3b2718b34770a"}, + {file = "orjson-3.8.12-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4fd240e736ce52cd757d74142d9933fd35a3184396be887c435f0574e0388654"}, + {file = "orjson-3.8.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6cae2ff288a80e81ce30313e735c5436495ab58cf8d4fbe84900e616d0ee7a78"}, + {file = "orjson-3.8.12-cp38-none-win_amd64.whl", hash = "sha256:710c40c214b753392e46f9275fd795e9630dd737a5ab4ac6e4ee1a02fe83cc0d"}, + {file = "orjson-3.8.12-cp39-cp39-macosx_11_0_x86_64.macosx_11_0_arm64.macosx_11_0_universal2.whl", hash = "sha256:aff761de5ed5543a0a51e9f703668624749aa2239de5d7d37d9c9693daeaf5dc"}, + {file = "orjson-3.8.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:135f29cf936283a0cd1b8bce86540ca181108f2a4d4483eedad6b8026865d2a9"}, + {file = "orjson-3.8.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f999798f2fa55e567d483864ebfc30120fb055c2696a255979439323a5b15c"}, + {file = "orjson-3.8.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fa58ca064c640fa9d823f98fbbc8e71940ecb78cea3ac2507da1cbf49d60b51"}, + {file = "orjson-3.8.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8682f752c19f6a7d9fc727fd98588b4c8b0dce791b5794bb814c7379ccd64a79"}, + {file = "orjson-3.8.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3d096dde3e46d01841abc1982b906694ab3c92f338d37a2e6184739dc8a958"}, + {file = "orjson-3.8.12-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:834b50df79f1fe89bbaced3a1c1d8c8c92cc99e84cdcd374d8da4974b3560d2a"}, + {file = "orjson-3.8.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2ad149ed76dce2bbdfbadd61c35959305e77141badf364a158beb4ef3d88ec37"}, + {file = "orjson-3.8.12-cp39-none-win_amd64.whl", hash = "sha256:82d65e478a21f98107b4eb8390104746bb3024c27084b57edab7d427385f1f70"}, + {file = "orjson-3.8.12.tar.gz", hash = "sha256:9f0f042cf002a474a6aea006dd9f8d7a5497e35e5fb190ec78eb4d232ec19955"}, ] [[package]] name = "packaging" -version = "23.0" +version = "23.1" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] [[package]] name = "pathspec" -version = "0.10.3" +version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, - {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, ] [[package]] @@ -920,22 +1016,22 @@ pytzdata = ">=2020.1" [[package]] name = "platformdirs" -version = "2.6.2" +version = "3.5.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, - {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, + {file = "platformdirs-3.5.1-py3-none-any.whl", hash = "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5"}, + {file = "platformdirs-3.5.1.tar.gz", hash = "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f"}, ] [package.dependencies] -typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} +typing-extensions = {version = ">=4.5", markers = "python_version < \"3.8\""} [package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.2.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" @@ -958,26 +1054,26 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "psutil" -version = "5.9.4" +version = "5.9.5" description = "Cross-platform lib for process and system monitoring in Python." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, - {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, - {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, - {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, - {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, - {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, - {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, - {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, - {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, - {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, - {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, - {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, - {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, - {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, + {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, + {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, + {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, + {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, + {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, + {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, + {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, + {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, ] [package.extras] @@ -1033,14 +1129,14 @@ files = [ [[package]] name = "pygments" -version = "2.14.0" +version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, - {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, ] [package.extras] @@ -1161,18 +1257,17 @@ dev = ["importlib-metadata", "tox"] [[package]] name = "pytest" -version = "7.2.0" +version = "7.3.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, - {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, + {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, + {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, ] [package.dependencies] -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -1182,7 +1277,7 @@ pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-benchmark" @@ -1222,14 +1317,14 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2022.7" +version = "2023.3" description = "World timezone definitions, modern and historical" category = "dev" optional = false python-versions = "*" files = [ - {file = "pytz-2022.7-py2.py3-none-any.whl", hash = "sha256:93007def75ae22f7cd991c84e02d434876818661f8df9ad5df9e950ff4e52cfd"}, - {file = "pytz-2022.7.tar.gz", hash = "sha256:7ccfae7b4b2c067464a6733c6261673fdb8fd1be905460396b97a073e9fa683a"}, + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] [[package]] @@ -1296,25 +1391,25 @@ files = [ [[package]] name = "requests" -version = "2.27.1" +version = "2.30.0" description = "Python HTTP for Humans." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7" files = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, + {file = "requests-2.30.0-py3-none-any.whl", hash = "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294"}, + {file = "requests-2.30.0.tar.gz", hash = "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} -urllib3 = ">=1.21.1,<1.27" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "six" @@ -1354,14 +1449,14 @@ files = [ [[package]] name = "soupsieve" -version = "2.3.2.post1" +version = "2.4.1" description = "A modern CSS selector implementation for Beautiful Soup." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, - {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, ] [[package]] @@ -1420,14 +1515,14 @@ docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-ta [[package]] name = "sphinx-copybutton" -version = "0.5.1" +version = "0.5.2" description = "Add a copy button to each of your code cells." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "sphinx-copybutton-0.5.1.tar.gz", hash = "sha256:366251e28a6f6041514bfb5439425210418d6c750e98d3a695b73e56866a677a"}, - {file = "sphinx_copybutton-0.5.1-py3-none-any.whl", hash = "sha256:0842851b5955087a7ec7fc870b622cb168618ad408dee42692e9a5c97d071da8"}, + {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, + {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, ] [package.dependencies] @@ -1546,14 +1641,14 @@ files = [ [[package]] name = "tomlkit" -version = "0.11.6" +version = "0.11.8" description = "Style preserving TOML library" category = "main" optional = true -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, - {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, + {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, + {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, ] [[package]] @@ -1619,14 +1714,14 @@ files = [ [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, ] [[package]] @@ -1706,14 +1801,14 @@ files = [ [[package]] name = "urllib3" -version = "1.26.13" +version = "1.26.15" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, - {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, + {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, + {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, ] [package.extras] @@ -1723,41 +1818,41 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.17.1" +version = "20.23.0" description = "Virtual Python Environment builder" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"}, - {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"}, + {file = "virtualenv-20.23.0-py3-none-any.whl", hash = "sha256:6abec7670e5802a528357fdc75b26b9f57d5d92f29c5462ba0fbe45feacc685e"}, + {file = "virtualenv-20.23.0.tar.gz", hash = "sha256:a85caa554ced0c0afbd0d638e7e2d7b5f92d23478d05d17a76daeac8f279f924"}, ] [package.dependencies] distlib = ">=0.3.6,<1" -filelock = ">=3.4.1,<4" -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} -platformdirs = ">=2.4,<3" +filelock = ">=3.11,<4" +importlib-metadata = {version = ">=6.4.1", markers = "python_version < \"3.8\""} +platformdirs = ">=3.2,<4" [package.extras] -docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] -testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.7.1)", "time-machine (>=2.9)"] [[package]] name = "zipp" -version = "3.11.0" +version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] bson = ["pymongo"] @@ -1771,4 +1866,4 @@ ujson = ["ujson"] [metadata] lock-version = "2.0" python-versions = ">= 3.7" -content-hash = "b7bfe9dbd804e99f0c3b674050302cc9ec8c90f63b3496d91e1de593af6ed967" +content-hash = "23f865b923ec3185ea66ac9b24ebd0f2f5e65e516eddac1d6eec0c92914e11fe" diff --git a/pyproject.toml b/pyproject.toml index 814313ee..4d45b2fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ readme = "README.md" [tool.poetry.dependencies] python = ">= 3.7" attrs = ">= 20" -typing_extensions = { version = "*", python = "< 3.8" } +typing_extensions = { version = "*", python = "< 3.10" } exceptiongroup = { version = "*", python = "< 3.11" } ujson = { version = "^5.4.0", optional = true } orjson = { version = "^3.5.2", markers = "implementation_name == 'cpython'", optional = true } diff --git a/src/cattrs/_compat.py b/src/cattrs/_compat.py index 36be2f5b..026e61f4 100644 --- a/src/cattrs/_compat.py +++ b/src/cattrs/_compat.py @@ -20,11 +20,33 @@ from attr import fields as attrs_fields from attr import resolve_types +try: + from typing_extensions import TypedDict as ExtensionsTypedDict +except ImportError: + ExtensionsTypedDict = None + +try: + from typing_extensions import _TypedDictMeta as ExtensionsTypedDictMeta +except ImportError: + ExtensionsTypedDictMeta = None + +__all__ = [ + "ExtensionsTypedDict", + "is_py37", + "is_py38", + "is_py39_plus", + "is_py310_plus", + "is_py311_plus", + "is_typeddict", + "TypedDict", +] + version_info = sys.version_info[0:3] is_py37 = version_info[:2] == (3, 7) is_py38 = version_info[:2] == (3, 8) is_py39_plus = version_info[:2] >= (3, 9) is_py310_plus = version_info[:2] >= (3, 10) +is_py311_plus = version_info[:2] >= (3, 11) if is_py37: @@ -37,7 +59,7 @@ def get_origin(cl): from typing_extensions import Final, Protocol else: - from typing import Final, Protocol, get_args, get_origin # NOQA + from typing import Final, Protocol, get_args, get_origin if "ExceptionGroup" not in dir(builtins): from exceptiongroup import ExceptionGroup @@ -64,7 +86,7 @@ def fields(type): raise Exception("Not an attrs or dataclass class.") -def adapted_fields(cl) -> List[Attribute]: +def _adapted_fields(cl) -> List[Attribute]: """Return the attrs format of `fields()` for attrs and dataclasses.""" if is_dataclass(cl): attrs = dataclass_fields(cl) @@ -152,6 +174,14 @@ def get_final_base(type) -> Optional[type]: from collections import Counter as ColCounter from typing import Counter, Union, _GenericAlias + if is_py38: + from typing import TypedDict, _TypedDictMeta + else: + _TypedDictMeta = None + TypedDict = ExtensionsTypedDict + + from typing_extensions import NotRequired, Required + def is_annotated(_): return False @@ -238,6 +268,25 @@ def copy_with(type, args): """Replace a generic type's arguments.""" return type.copy_with(args) + def is_typeddict(cls) -> bool: + return ( + cls.__class__ is _TypedDictMeta + or (is_generic(cls) and (cls.__origin__.__class__ is _TypedDictMeta)) + or ( + ExtensionsTypedDictMeta is not None + and cls.__class__ is ExtensionsTypedDictMeta + or ( + is_generic(cls) + and (cls.__origin__.__class__ is ExtensionsTypedDictMeta) + ) + ) + ) + + def get_notrequired_base(type) -> "Union[Any, Literal[NOTHING]]": + if get_origin(type) in (NotRequired, Required): + return get_args(type)[0] + return NOTHING + else: # 3.9+ from collections import Counter @@ -252,8 +301,7 @@ def copy_with(type, args): from typing import Counter as TypingCounter from typing import ( Generic, - NotRequired, - Required, + TypedDict, Union, _AnnotatedAlias, _GenericAlias, @@ -262,16 +310,6 @@ def copy_with(type, args): _UnionGenericAlias, ) - def is_typeddict(cls) -> bool: - return cls.__class__ is _TypedDictMeta or ( - is_generic(cls) and (cls.__origin__.__class__ is _TypedDictMeta) - ) - - def get_notrequired_base(type) -> "Union[Any, Literal[NOTHING]]": - if get_origin(type) in (NotRequired, Required): - return get_args(type)[0] - return NOTHING - try: # Not present on 3.9.0, so we try carefully. from typing import _LiteralGenericAlias @@ -320,7 +358,13 @@ def get_newtype_base(typ: Any) -> Optional[type]: return typ.__supertype__ return None + if is_py311_plus: + from typing import NotRequired, Required + else: + from typing_extensions import NotRequired, Required + else: + from typing_extensions import NotRequired, Required def is_union_type(obj): return ( @@ -339,6 +383,25 @@ def get_newtype_base(typ: Any) -> Optional[type]: return supertype return None + def is_typeddict(cls) -> bool: + return ( + cls.__class__ is _TypedDictMeta + or (is_generic(cls) and (cls.__origin__.__class__ is _TypedDictMeta)) + or ( + ExtensionsTypedDictMeta is not None + and cls.__class__ is ExtensionsTypedDictMeta + or ( + is_generic(cls) + and (cls.__origin__.__class__ is ExtensionsTypedDictMeta) + ) + ) + ) + + def get_notrequired_base(type) -> "Union[Any, Literal[NOTHING]]": + if get_origin(type) in (NotRequired, Required): + return get_args(type)[0] + return NOTHING + def is_sequence(type: Any) -> bool: origin = getattr(type, "__origin__", None) return ( diff --git a/src/cattrs/gen/__init__.py b/src/cattrs/gen/__init__.py index 1ea1e725..2f93ee5c 100644 --- a/src/cattrs/gen/__init__.py +++ b/src/cattrs/gen/__init__.py @@ -20,7 +20,7 @@ from attr import NOTHING, resolve_types from .._compat import ( - adapted_fields, + _adapted_fields, get_args, get_origin, is_annotated, @@ -71,7 +71,7 @@ def make_dict_unstructure_fn( dataclass. """ origin = get_origin(cl) - attrs = adapted_fields(origin or cl) # type: ignore + attrs = _adapted_fields(origin or cl) # type: ignore if any(isinstance(a.type, str) for a in attrs): # PEP 563 annotations - need to be resolved. @@ -272,7 +272,7 @@ def make_dict_structure_fn( post_lines = [] invocation_lines = [] - attrs = adapted_fields(cl) + attrs = _adapted_fields(cl) is_dc = is_dataclass(cl) if any(isinstance(a.type, str) for a in attrs): diff --git a/src/cattrs/gen/typeddicts.py b/src/cattrs/gen/typeddicts.py index f9f27b00..6e2b0153 100644 --- a/src/cattrs/gen/typeddicts.py +++ b/src/cattrs/gen/typeddicts.py @@ -2,17 +2,39 @@ import linecache import re -from inspect import get_annotations from typing import TYPE_CHECKING, Any, Callable, Dict, List, Type, TypeVar from attr import NOTHING, Attribute +try: + from inspect import get_annotations + + def get_annots(cl): + return get_annotations(cl, eval_str=True) + +except ImportError: + # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older + def get_annots(cl): + if isinstance(cl, type): + ann = cl.__dict__.get("__annotations__", {}) + else: + ann = getattr(cl, "__annotations__", {}) + return ann + + +try: + from typing_extensions import _TypedDictMeta +except ImportError: + _TypedDictMeta = None + from .._compat import ( get_notrequired_base, get_origin, is_annotated, is_bare, is_generic, + is_py39_plus, + is_py311_plus, ) from .._generics import deep_copy_with from ..errors import ( @@ -30,6 +52,8 @@ if TYPE_CHECKING: # pragma: no cover from cattr.converters import BaseConverter +__all__ = ["make_dict_unstructure_fn", "make_dict_structure_fn"] + T = TypeVar("T") @@ -43,8 +67,8 @@ def make_dict_unstructure_fn( Generate a specialized dict unstructuring function for a TypedDict. """ origin = get_origin(cl) - attrs = adapted_fields(origin or cl) # type: ignore - req_keys = (origin or cl).__required_keys__ + attrs = _adapted_fields(origin or cl) # type: ignore + req_keys = _required_keys(origin or cl) mapping = {} if is_generic(cl): @@ -235,7 +259,6 @@ def make_dict_structure_fn( cl_name = cl.__name__ fn_name = "structure_" + cl_name - req_keys = cl.__required_keys__ # We have generic parameters and need to generate a unique name for the function for p in getattr(cl, "__parameters__", ()): @@ -259,7 +282,9 @@ def make_dict_structure_fn( lines = [] post_lines = [] - attrs = adapted_fields(cl) + attrs = _adapted_fields(cl) + req_keys = _required_keys(cl) + print(req_keys) allowed_fields = set() if _cattrs_forbid_extra_keys: @@ -472,9 +497,87 @@ def make_dict_structure_fn( return globs[fn_name] -def adapted_fields(cls: Any) -> List[Attribute]: - annotations = get_annotations(cls, eval_str=True) +def _adapted_fields(cls: Any) -> List[Attribute]: + annotations = get_annots(cls) return [ Attribute(n, NOTHING, None, False, False, False, False, False, type=a) for n, a in annotations.items() ] + + +def _is_extensions_typeddict(cls) -> bool: + if _TypedDictMeta is None: + return False + return cls.__class__ is _TypedDictMeta or ( + is_generic(cls) and (cls.__origin__.__class__ is _TypedDictMeta) + ) + + +if is_py311_plus: + + def _required_keys(cls: Type) -> set[str]: + return cls.__required_keys__ + +elif is_py39_plus: + from typing_extensions import Annotated, NotRequired, Required, get_args + + def _required_keys(cls: Type) -> set[str]: + if _is_extensions_typeddict(cls): + return cls.__required_keys__ + else: + # We vendor a part of the typing_extensions logic for + # gathering required keys. *sigh* + own_annotations = cls.__dict__.get("__annotations__", {}) + required_keys = set() + for base in cls.__mro__[1:]: + required_keys |= _required_keys(base) + for key in getattr(cls, "__required_keys__", []): + annotation_type = own_annotations[key] + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + annotation_origin = get_origin(annotation_type) + + if annotation_origin is Required: + required_keys.add(key) + elif annotation_origin is NotRequired: + pass + elif getattr(cls, "__total__"): + required_keys.add(key) + return required_keys + +else: + from typing_extensions import Annotated, NotRequired, Required, get_args + + # On 3.8, typing.TypedDicts do not have __required_keys__. + + def _required_keys(cls: Type) -> set[str]: + if _is_extensions_typeddict(cls): + return cls.__required_keys__ + else: + own_annotations = cls.__dict__.get("__annotations__", {}) + required_keys = set() + superclass_keys = set() + for base in cls.__mro__[1:]: + required_keys |= _required_keys(base) + superclass_keys |= base.__dict__.get("__annotations__", {}).keys() + for key in own_annotations: + if key in superclass_keys: + continue + annotation_type = own_annotations[key] + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + annotation_origin = get_origin(annotation_type) + + if annotation_origin is Required: + required_keys.add(key) + elif annotation_origin is NotRequired: + pass + elif getattr(cls, "__total__"): + required_keys.add(key) + return required_keys diff --git a/tests/test_gen_dict.py b/tests/test_gen_dict.py index 0642f350..6cdf08cb 100644 --- a/tests/test_gen_dict.py +++ b/tests/test_gen_dict.py @@ -8,7 +8,7 @@ from hypothesis.strategies import data, just, one_of, sampled_from from cattrs import BaseConverter, Converter -from cattrs._compat import adapted_fields, fields, is_py39_plus +from cattrs._compat import _adapted_fields, fields, is_py39_plus from cattrs.errors import ClassValidationError, ForbiddenExtraKeysError from cattrs.gen import make_dict_structure_fn, make_dict_unstructure_fn, override @@ -116,7 +116,7 @@ def test_individual_overrides(converter_cls, cl_and_vals): converter = converter_cls() cl, vals, kwargs = cl_and_vals - for attr, val in zip(adapted_fields(cl), vals): + for attr, val in zip(_adapted_fields(cl), vals): if attr.default is not NOTHING: break else: @@ -140,7 +140,7 @@ def test_individual_overrides(converter_cls, cl_and_vals): assert "Hyp" not in repr(res) assert "Factory" not in repr(res) - for attr, val in zip(adapted_fields(cl), vals): + for attr, val in zip(_adapted_fields(cl), vals): if attr.name == chosen_name: assert attr.name in res elif attr.default is not NOTHING: diff --git a/tests/test_generics.py b/tests/test_generics.py index 27c9bb58..2e95d9a1 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -4,7 +4,7 @@ from attr import asdict, attrs, define from cattrs import BaseConverter, Converter -from cattrs._compat import Protocol, is_py39_plus, is_py310_plus +from cattrs._compat import Protocol, is_py39_plus, is_py310_plus, is_py311_plus from cattrs._generics import deep_copy_with from cattrs.errors import StructureHandlerNotFoundError from cattrs.gen._generics import generate_mapping @@ -267,6 +267,7 @@ class Outer(Generic[T]): assert c.structure(raw, Outer[A | B]) == Outer((A(1))) +@pytest.mark.skipif(not is_py311_plus, reason="3.11+ only") def test_generate_typeddict_mapping() -> None: from typing import Generic, TypedDict, TypeVar diff --git a/tests/test_typeddicts.py b/tests/test_typeddicts.py index f0a2b8c2..9bde88e7 100644 --- a/tests/test_typeddicts.py +++ b/tests/test_typeddicts.py @@ -1,17 +1,22 @@ """Tests for TypedDict un/structuring.""" from datetime import datetime -from inspect import get_annotations +from typing import Dict, Set, Tuple +import pytest from hypothesis import assume, given from hypothesis.strategies import booleans from pytest import raises from cattrs import Converter -from cattrs._compat import is_generic +from cattrs._compat import ExtensionsTypedDict, is_generic, is_py38, is_py311_plus from cattrs.errors import ClassValidationError, ForbiddenExtraKeysError from cattrs.gen import override from cattrs.gen._generics import generate_mapping -from cattrs.gen.typeddicts import make_dict_structure_fn, make_dict_unstructure_fn +from cattrs.gen.typeddicts import ( + get_annots, + make_dict_structure_fn, + make_dict_unstructure_fn, +) from .typeddicts import ( generic_typeddicts, @@ -34,7 +39,7 @@ def get_annot(t) -> dict: # This will have typevars. origin = getattr(t, "__origin__", None) if origin is not None: - origin_annotations = get_annotations(origin) + origin_annotations = get_annots(origin) args = t.__args__ params = origin.__parameters__ param_to_args = dict(zip(params, args)) @@ -47,12 +52,12 @@ def get_annot(t) -> dict: mapping = generate_mapping(t) return { k: mapping[v.__name__] if v.__name__ in mapping else v - for k, v in get_annotations(t).items() + for k, v in get_annots(t).items() } - return get_annotations(t) + return get_annots(t) -@given(simple_typeddicts()) +@given(simple_typeddicts(typeddict_cls=None if not is_py38 else ExtensionsTypedDict)) def test_simple_roundtrip(cls_and_instance) -> None: """Round-trips for simple classes work.""" c = mk_converter() @@ -72,7 +77,12 @@ def test_simple_roundtrip(cls_and_instance) -> None: assert restructured == instance -@given(simple_typeddicts(total=False), booleans()) +@given( + simple_typeddicts( + total=False, typeddict_cls=None if not is_py38 else ExtensionsTypedDict + ), + booleans(), +) def test_simple_nontotal(cls_and_instance, detailed_validation: bool) -> None: """Non-total dicts work.""" c = mk_converter(detailed_validation=detailed_validation) @@ -92,7 +102,7 @@ def test_simple_nontotal(cls_and_instance, detailed_validation: bool) -> None: assert restructured == instance -@given(simple_typeddicts()) +@given(simple_typeddicts(typeddict_cls=None if not is_py38 else ExtensionsTypedDict)) def test_int_override(cls_and_instance) -> None: """Overriding a base unstructure handler should work.""" cls, instance = cls_and_instance @@ -108,9 +118,14 @@ def test_int_override(cls_and_instance) -> None: assert unstructured == instance -@given(simple_typeddicts_with_extra_keys(), booleans()) +@given( + simple_typeddicts_with_extra_keys( + typeddict_cls=None if not is_py38 else ExtensionsTypedDict + ), + booleans(), +) def test_extra_keys( - cls_instance_extra: tuple[type, dict, set[str]], detailed_validation: bool + cls_instance_extra: Tuple[type, Dict, Set[str]], detailed_validation: bool ) -> None: """Extra keys are preserved.""" cls, instance, extra = cls_instance_extra @@ -129,9 +144,10 @@ def test_extra_keys( assert structured == instance +@pytest.mark.skipif(not is_py311_plus, reason="3.11+ only") @given(generic_typeddicts(total=True), booleans()) def test_generics( - cls_and_instance: tuple[type, dict], detailed_validation: bool + cls_and_instance: Tuple[type, Dict], detailed_validation: bool ) -> None: """Generic TypedDicts work.""" c = mk_converter(detailed_validation=detailed_validation) @@ -153,7 +169,7 @@ def test_generics( @given(simple_typeddicts(total=True, not_required=True), booleans()) def test_not_required( - cls_and_instance: tuple[type, dict], detailed_validation: bool + cls_and_instance: Tuple[type, Dict], detailed_validation: bool ) -> None: """NotRequired[] keys are handled.""" c = mk_converter(detailed_validation=detailed_validation) @@ -165,9 +181,16 @@ def test_not_required( assert restructured == instance -@given(simple_typeddicts(total=False, not_required=True), booleans()) +@given( + simple_typeddicts( + total=False, + not_required=True, + typeddict_cls=None if not is_py38 else ExtensionsTypedDict, + ), + booleans(), +) def test_required( - cls_and_instance: tuple[type, dict], detailed_validation: bool + cls_and_instance: Tuple[type, Dict], detailed_validation: bool ) -> None: """Required[] keys are handled.""" c = mk_converter(detailed_validation=detailed_validation) @@ -180,7 +203,7 @@ def test_required( @given(simple_typeddicts(min_attrs=1, total=True), booleans()) -def test_omit(cls_and_instance: tuple[type, dict], detailed_validation: bool) -> None: +def test_omit(cls_and_instance: Tuple[type, Dict], detailed_validation: bool) -> None: """`override(omit=True)` works.""" c = mk_converter(detailed_validation=detailed_validation) @@ -222,7 +245,7 @@ def test_omit(cls_and_instance: tuple[type, dict], detailed_validation: bool) -> @given(simple_typeddicts(min_attrs=1, total=True), booleans()) -def test_rename(cls_and_instance: tuple[type, dict], detailed_validation: bool) -> None: +def test_rename(cls_and_instance: Tuple[type, Dict], detailed_validation: bool) -> None: """`override(rename=...)` works.""" c = mk_converter(detailed_validation=detailed_validation) @@ -259,7 +282,7 @@ def test_rename(cls_and_instance: tuple[type, dict], detailed_validation: bool) @given(simple_typeddicts(total=True), booleans()) def test_forbid_extra_keys( - cls_and_instance: tuple[type, dict], detailed_validation: bool + cls_and_instance: Tuple[type, Dict], detailed_validation: bool ) -> None: """Extra keys can be forbidden.""" c = mk_converter(detailed_validation) diff --git a/tests/test_v.py b/tests/test_v.py index e0bd77d1..6bfac63a 100644 --- a/tests/test_v.py +++ b/tests/test_v.py @@ -7,14 +7,13 @@ Optional, Sequence, Tuple, - TypedDict, ) from attrs import Factory, define from pytest import fixture, raises from cattrs import Converter, transform_error -from cattrs._compat import Mapping +from cattrs._compat import Mapping, TypedDict from cattrs.gen import make_dict_structure_fn from cattrs.v import format_exception diff --git a/tests/typeddicts.py b/tests/typeddicts.py index f9a421f9..43136957 100644 --- a/tests/typeddicts.py +++ b/tests/typeddicts.py @@ -1,7 +1,7 @@ """Strategies for typed dicts.""" from datetime import datetime from string import ascii_lowercase -from typing import Generic, List, NotRequired, Optional, Required, TypedDict, TypeVar +from typing import Any, Dict, Generic, List, Optional, Set, Tuple, TypeVar from attr import NOTHING from hypothesis.strategies import ( @@ -17,6 +17,8 @@ text, ) +from cattrs._compat import ExtensionsTypedDict, NotRequired, Required, TypedDict + from .untyped import gen_attr_names # Type aliases for readability @@ -29,7 +31,7 @@ @composite def int_attributes( draw: DrawFn, total: bool = True, not_required: bool = False -) -> tuple[int, SearchStrategy, SearchStrategy]: +) -> Tuple[int, SearchStrategy, SearchStrategy]: if total: if not_required and draw(booleans()): return (NotRequired[int], integers() | just(NOTHING), text(ascii_lowercase)) @@ -45,7 +47,7 @@ def int_attributes( @composite def datetime_attributes( draw: DrawFn, total: bool = True, not_required: bool = False -) -> tuple[datetime, SearchStrategy, SearchStrategy]: +) -> Tuple[datetime, SearchStrategy, SearchStrategy]: success_strat = datetimes().map(lambda dt: dt.replace(microsecond=0)) type = datetime strat = success_strat if total else success_strat | just(NOTHING) @@ -62,7 +64,7 @@ def datetime_attributes( @composite def list_of_int_attributes( draw: DrawFn, total: bool = True, not_required: bool = False -) -> tuple[List[int], SearchStrategy, SearchStrategy]: +) -> Tuple[List[int], SearchStrategy, SearchStrategy]: if total: if not_required and draw(booleans()): return ( @@ -97,7 +99,8 @@ def simple_typeddicts( total: Optional[bool] = None, not_required: bool = False, min_attrs: int = 0, -) -> tuple[TypedDictType, dict]: + typeddict_cls: Optional[Any] = None, +) -> Tuple[TypedDictType, dict]: """Generate simple typed dicts. :param total: Generate the given totality dicts (default = random) @@ -121,7 +124,12 @@ def simple_typeddicts( if v is not NOTHING: success_payload[n] = v - cls = TypedDict("HypTypedDict", attrs_dict, total=total) + if typeddict_cls is None: + cls = (TypedDict if draw(booleans()) else ExtensionsTypedDict)( + "HypTypedDict", attrs_dict, total=total + ) + else: + cls = typeddict_cls if draw(booleans()): @@ -136,10 +144,10 @@ class InheritedTypedDict(cls): @composite def simple_typeddicts_with_extra_keys( - draw: DrawFn, total: Optional[bool] = None -) -> tuple[TypedDictType, dict, set[str]]: + draw: DrawFn, total: Optional[bool] = None, typeddict_cls: Optional[Any] = None +) -> Tuple[TypedDictType, dict, Set[str]]: """Generate TypedDicts, with the instances having extra keys.""" - cls, success = draw(simple_typeddicts(total)) + cls, success = draw(simple_typeddicts(total, typeddict_cls=typeddict_cls)) # The normal attributes are 2 characters or less. extra_keys = draw(sets(text(ascii_lowercase, min_size=3, max_size=3))) @@ -151,7 +159,7 @@ def simple_typeddicts_with_extra_keys( @composite def generic_typeddicts( draw: DrawFn, total: Optional[bool] = None -) -> tuple[TypedDictType, dict]: +) -> Tuple[TypedDictType, dict]: """Generate generic typed dicts. :param total: Generate the given totality dicts (default = random) @@ -189,24 +197,24 @@ def generic_typeddicts( attrs_dict[attr_name] = typevar cls = make_typeddict( - "HypTypedDict", attrs_dict, total=total, bases=[Generic[*generics]] + "HypTypedDict", attrs_dict, total=total, bases=[Generic[Tuple(generics)]] ) if draw(booleans()): - class InheritedTypedDict(cls[*actual_types]): + class InheritedTypedDict(cls[tuple(actual_types)]): inherited: int cls = InheritedTypedDict success_payload["inherited"] = draw(integers()) else: - cls = cls[*actual_types] + cls = cls[tuple(actual_types)] return (cls, success_payload) def make_typeddict( - cls_name: str, attrs: dict[str, type], total: bool = True, bases: list = [] + cls_name: str, attrs: Dict[str, type], total: bool = True, bases: List = [] ) -> TypedDictType: globs = {"TypedDict": TypedDict} lines = [] From 6851246f0b3f14722b7ef8367db0127065cb2d03 Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Fri, 19 May 2023 01:27:51 +0200 Subject: [PATCH 7/8] 3.11 fix --- tests/typeddicts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/typeddicts.py b/tests/typeddicts.py index 43136957..2928e972 100644 --- a/tests/typeddicts.py +++ b/tests/typeddicts.py @@ -197,7 +197,7 @@ def generic_typeddicts( attrs_dict[attr_name] = typevar cls = make_typeddict( - "HypTypedDict", attrs_dict, total=total, bases=[Generic[Tuple(generics)]] + "HypTypedDict", attrs_dict, total=total, bases=[Generic[tuple(generics)]] ) if draw(booleans()): From b17206089d88459338b7a88b60ce765e43765fec Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Sat, 20 May 2023 19:51:27 +0200 Subject: [PATCH 8/8] Docs --- HISTORY.md | 2 ++ docs/cattrs.gen.rst | 21 +++++++++++++ docs/cattrs.rst | 9 +----- docs/structuring.md | 58 +++++++++++++++++++++++++++++++++- docs/unstructuring.md | 60 +++++++++++++++++++++++++++++++++++- src/cattrs/gen/typeddicts.py | 19 ++++++++++-- 6 files changed, 156 insertions(+), 13 deletions(-) create mode 100644 docs/cattrs.gen.rst diff --git a/HISTORY.md b/HISTORY.md index cf2fd351..c9fa1ee6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,8 @@ - Introduce the `tagged_union` strategy. ([#318](https://github.com/python-attrs/cattrs/pull/318) [#317](https://github.com/python-attrs/cattrs/issues/317)) - Introduce the `cattrs.transform_error` helper function for formatting validation exceptions. ([258](https://github.com/python-attrs/cattrs/issues/258) [342](https://github.com/python-attrs/cattrs/pull/342)) +- Add support for [`typing.TypedDict` and `typing_extensions.TypedDict`](https://peps.python.org/pep-0589/). + ([#296](https://github.com/python-attrs/cattrs/issues/296) [#364](https://github.com/python-attrs/cattrs/pull/364)) - Add support for `typing.Final`. ([#340](https://github.com/python-attrs/cattrs/issues/340) [#349](https://github.com/python-attrs/cattrs/pull/349)) - Introduce `override.struct_hook` and `override.unstruct_hook`. Learn more [here](https://catt.rs/en/latest/customizing.html#struct-hook-and-unstruct-hook). diff --git a/docs/cattrs.gen.rst b/docs/cattrs.gen.rst new file mode 100644 index 00000000..1968fcae --- /dev/null +++ b/docs/cattrs.gen.rst @@ -0,0 +1,21 @@ +cattrs.gen package +================== + +Submodules +---------- + +cattrs.gen.typeddicts module +---------------------------- + +.. automodule:: cattrs.gen.typeddicts + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: cattrs.gen + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/cattrs.rst b/docs/cattrs.rst index 233119be..4c82a09c 100644 --- a/docs/cattrs.rst +++ b/docs/cattrs.rst @@ -7,6 +7,7 @@ Subpackages .. toctree:: :maxdepth: 4 + cattrs.gen cattrs.preconf cattrs.strategies @@ -45,14 +46,6 @@ cattrs.errors module :undoc-members: :show-inheritance: -cattrs.gen module ------------------ - -.. automodule:: cattrs.gen - :members: - :undoc-members: - :show-inheritance: - cattrs.v module --------------- diff --git a/docs/structuring.md b/docs/structuring.md index 09200309..1d4dd19f 100644 --- a/docs/structuring.md +++ b/docs/structuring.md @@ -211,6 +211,62 @@ and values can be converted. {'1': None, '2': 2} ``` +### Typed Dicts + +[TypedDicts](https://peps.python.org/pep-0589/) can be produced from mapping objects, usually dictionaries. + +```{doctest} +>>> from typing import TypedDict + +>>> class MyTypedDict(TypedDict): +... a: int + +>>> cattrs.structure({"a": "1"}, MyTypedDict) +{'a': 1} +``` + +Both [_total_ and _non-total_](https://peps.python.org/pep-0589/#totality) TypedDicts are supported, and inheritance between any combination works (except on 3.8 when `typing.TypedDict` is used, see below). +Generic TypedDicts work on Python 3.11 and later, since that is the first Python version that supports them in general. + +[`typing.Required` and `typing.NotRequired`](https://peps.python.org/pep-0655/) are supported. + +On Python 3.7, using `typing_extensions.TypedDict` is required since `typing.TypedDict` doesn't exist there. +On Python 3.8, using `typing_extensions.TypedDict` is recommended since `typing.TypedDict` doesn't support all necessary features, so certain combinations of subclassing, totality and `typing.Required` won't work. + +[Similar to _attrs_ classes](customizing.md#using-cattrsgen-generators), structuring can be customized using {meth}`cattrs.gen.typeddicts.make_dict_structure_fn`. + +```{doctest} +>>> from typing import TypedDict +>>> from cattrs import Converter +>>> from cattrs.gen import override +>>> from cattrs.gen.typeddicts import make_dict_structure_fn + +>>> class MyTypedDict(TypedDict): +... a: int +... b: int + +>>> c = Converter() +>>> c.register_structure_hook( +... MyTypedDict, +... make_dict_structure_fn( +... MyTypedDict, +... c, +... a=override(rename="a-with-dash") +... ) +... ) + +>>> c.structure({"a-with-dash": 1, "b": 2}, MyTypedDict) +{'b': 2, 'a': 1} +``` + +```{seealso} [Unstructuring TypedDicts.](unstructuring.md#typed-dicts) + +``` + +```{versionadded} 23.1.0 + +``` + ### Homogeneous and Heterogeneous Tuples Homogeneous and heterogeneous tuples can be produced from iterable objects. @@ -436,7 +492,7 @@ annotations when using Python 3.6+, or by passing the appropriate type to ... a: int >>> attr.fields(A).a -Attribute(name='a', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=, converter=None, kw_only=False, inherited=False, on_setattr=None) +Attribute(name='a', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='a') ``` Type information, when provided, can be used for all attribute types, not only diff --git a/docs/unstructuring.md b/docs/unstructuring.md index 5e606b2d..03cca8c9 100644 --- a/docs/unstructuring.md +++ b/docs/unstructuring.md @@ -31,6 +31,64 @@ True False ``` +### Typed Dicts + +[TypedDicts](https://peps.python.org/pep-0589/) unstructure into dictionaries, potentially unchanged (depending on the exact field types and registered hooks). + +```{doctest} +>>> from typing import TypedDict +>>> from datetime import datetime, timezone +>>> from cattrs import Converter + +>>> class MyTypedDict(TypedDict): +... a: datetime + +>>> c = Converter() +>>> c.register_unstructure_hook(datetime, lambda d: d.timestamp()) + +>>> c.unstructure({"a": datetime(1970, 1, 1, tzinfo=timezone.utc)}, unstructure_as=MyTypedDict) +{'a': 0.0} +``` + +Generic TypedDicts work on Python 3.11 and later, since that is the first Python version that supports them in general. + +On Python 3.7, using `typing_extensions.TypedDict` is required since `typing.TypedDict` doesn't exist there. +On Python 3.8, using `typing_extensions.TypedDict` is recommended since `typing.TypedDict` doesn't support all necessary features, so certain combinations of subclassing, totality and `typing.Required` won't work. + +[Similar to _attrs_ classes](customizing.md#using-cattrsgen-generators), unstructuring can be customized using {meth}`cattrs.gen.typeddicts.make_dict_unstructure_fn`. + +```{doctest} +>>> from typing import TypedDict +>>> from cattrs import Converter +>>> from cattrs.gen import override +>>> from cattrs.gen.typeddicts import make_dict_unstructure_fn + +>>> class MyTypedDict(TypedDict): +... a: int +... b: int + +>>> c = Converter() +>>> c.register_unstructure_hook( +... MyTypedDict, +... make_dict_unstructure_fn( +... MyTypedDict, +... c, +... a=override(omit=True) +... ) +... ) + +>>> c.unstructure({"a": 1, "b": 2}, unstructure_as=MyTypedDict) +{'b': 2} +``` + +```{seealso} [Structuring TypedDicts.](structuring.md#typed-dicts) + +``` + +```{versionadded} 23.1.0 + +``` + ## `pathlib.Path` [`pathlib.Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path) objects are unstructured into their string value. @@ -57,7 +115,7 @@ generic way. A common example is using a JSON library that doesn't support sets, but expects lists and tuples instead. Using ordinary unstructuring hooks for this is unwieldy due to the semantics of -{ref}`singledispatch `; +[singledispatch](https://docs.python.org/3/library/functools.html#functools.singledispatch); in other words, you'd need to register hooks for all specific types of set you're using (`set[int]`, `set[float]`, `set[str]`...), which is not useful. diff --git a/src/cattrs/gen/typeddicts.py b/src/cattrs/gen/typeddicts.py index 6e2b0153..df6b1735 100644 --- a/src/cattrs/gen/typeddicts.py +++ b/src/cattrs/gen/typeddicts.py @@ -28,6 +28,7 @@ def get_annots(cl): _TypedDictMeta = None from .._compat import ( + TypedDict, get_notrequired_base, get_origin, is_annotated, @@ -54,7 +55,7 @@ def get_annots(cl): __all__ = ["make_dict_unstructure_fn", "make_dict_structure_fn"] -T = TypeVar("T") +T = TypeVar("T", bound=TypedDict) def make_dict_unstructure_fn( @@ -65,6 +66,11 @@ def make_dict_unstructure_fn( ) -> Callable[[T], Dict[str, Any]]: """ Generate a specialized dict unstructuring function for a TypedDict. + + :param cl: A `TypedDict` class. + :param converter: A Converter instance to use for unstructuring nested fields. + :param kwargs: A mapping of field names to an `AttributeOverride`, for customization. + :param _cattrs_detailed_validation: Whether to store the generated code in the _linecache_, for easier debugging and better stack traces. """ origin = get_origin(cl) attrs = _adapted_fields(origin or cl) # type: ignore @@ -238,7 +244,15 @@ def make_dict_structure_fn( _cattrs_detailed_validation: bool = True, **kwargs: AttributeOverride, ) -> Callable[[Dict, Any], Any]: - """Generate a specialized dict structuring function for typed dicts.""" + """Generate a specialized dict structuring function for typed dicts. + + :param cl: A `TypedDict` class. + :param converter: A Converter instance to use for structuring nested fields. + :param kwargs: A mapping of field names to an `AttributeOverride`, for customization. + :param _cattrs_detailed_validation: Whether to use a slower mode that produces more detailed errors. + :param _cattrs_forbid_extra_keys: Whether the structuring function should raise a `ForbiddenExtraKeysError` if unknown keys are encountered. + :param _cattrs_detailed_validation: Whether to store the generated code in the _linecache_, for easier debugging and better stack traces. + """ mapping = {} if is_generic(cl): @@ -284,7 +298,6 @@ def make_dict_structure_fn( attrs = _adapted_fields(cl) req_keys = _required_keys(cl) - print(req_keys) allowed_fields = set() if _cattrs_forbid_extra_keys: