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/poetry.lock b/poetry.lock index e4620629..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] @@ -67,37 +70,37 @@ lxml = ["lxml"] [[package]] name = "black" -version = "23.1.0" +version = "23.3.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-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] @@ -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.65.2" +version = "6.75.3" 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.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] @@ -684,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] @@ -716,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]] @@ -817,80 +901,82 @@ testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", [[package]] name = "orjson" -version = "3.8.5" +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.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.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.11.0" +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.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, - {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, ] [[package]] @@ -930,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" @@ -968,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] @@ -1043,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] @@ -1171,18 +1257,17 @@ dev = ["importlib-metadata", "tox"] [[package]] name = "pytest" -version = "7.2.1" +version = "7.3.1" 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.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\""} @@ -1192,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" @@ -1232,14 +1317,14 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2022.7.1" +version = "2023.3" 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-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] [[package]] @@ -1306,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" @@ -1364,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]] @@ -1430,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] @@ -1556,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]] @@ -1629,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]] @@ -1716,14 +1801,14 @@ files = [ [[package]] name = "urllib3" -version = "1.26.14" +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.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, - {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, + {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] @@ -1733,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.12.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.12.0-py3-none-any.whl", hash = "sha256:9eb0a4c5feab9b08871db0d672745b53450d7f26992fd1e4653aa43345e97b86"}, - {file = "zipp-3.12.0.tar.gz", hash = "sha256:73efd63936398aac78fd92b6f4865190119d6c91b531532e798977ea8dd402eb"}, + {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)", "sphinx-lint"] -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)"] +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"] @@ -1781,4 +1866,4 @@ ujson = ["ujson"] [metadata] lock-version = "2.0" python-versions = ">= 3.7" -content-hash = "f1917ee3a777dae10bb5db429c4aac4e597011e189c639011298ece395c2eea7" +content-hash = "23f865b923ec3185ea66ac9b24ebd0f2f5e65e516eddac1d6eec0c92914e11fe" diff --git a/pyproject.toml b/pyproject.toml index fc77ec31..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 } @@ -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 18106eff..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) @@ -103,6 +125,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__ @@ -144,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 @@ -230,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 @@ -243,10 +300,13 @@ def copy_with(type, args): from typing import Annotated from typing import Counter as TypingCounter from typing import ( + Generic, + TypedDict, Union, _AnnotatedAlias, _GenericAlias, _SpecialGenericAlias, + _TypedDictMeta, _UnionGenericAlias, ) @@ -298,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 ( @@ -317,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 ( @@ -388,8 +473,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/converters.py b/src/cattrs/converters.py index 64016c73..01708cc5 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,10 +50,16 @@ is_protocol, is_sequence, is_tuple, + is_typeddict, is_union_type, ) from .disambiguators import create_uniq_field_dis_func from .dispatch import MultiStrategyDispatch +from .errors import ( + IterableValidationError, + IterableValidationNote, + StructureHandlerNotFoundError, +) from .gen import ( AttributeOverride, DictStructureFn, @@ -74,6 +74,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 +867,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 +877,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 +901,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,10 +927,19 @@ 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]: - 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.py b/src/cattrs/gen/__init__.py similarity index 84% rename from src/cattrs/gen.py rename to src/cattrs/gen/__init__.py index 6bb4effd..2f93ee5c 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,19 +17,10 @@ ) 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 ( - adapted_fields, +from .._compat import ( + _adapted_fields, get_args, get_origin, is_annotated, @@ -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") @@ -81,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. @@ -89,13 +79,14 @@ 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 + if origin is not None: + cl = origin cl_name = cl.__name__ fn_name = "unstructure_" + cl_name @@ -107,10 +98,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 +110,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 +202,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 +217,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 +234,13 @@ def make_dict_structure_fn( mapping = {} if is_generic(cl): base = get_origin(cl) - mapping = _generate_mapping(cl, mapping) - cl = base + mapping = generate_mapping(cl, mapping) + 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"): - mapping = _generate_mapping(base, mapping) + mapping = generate_mapping(base, mapping) break if isinstance(cl, TypeVar): @@ -343,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): @@ -363,7 +292,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 +370,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 +421,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 +483,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 +754,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..a1161ea9 --- /dev/null +++ b/src/cattrs/gen/_generics.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from typing import Dict, Type, TypeVar + +from .._compat import get_args, get_origin, is_generic + + +def generate_mapping(cl: Type, old_mapping: Dict[str, type] = {}) -> Dict[str, type]: + 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__ + 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 + + 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..63e21cd8 --- /dev/null +++ b/src/cattrs/gen/_shared.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Callable, Optional + +from attr import NOTHING, Attribute, Factory + +from .._compat import is_bare_final + +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: + 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 new file mode 100644 index 00000000..df6b1735 --- /dev/null +++ b/src/cattrs/gen/typeddicts.py @@ -0,0 +1,596 @@ +from __future__ import annotations + +import linecache +import re +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 ( + TypedDict, + 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 ( + AttributeValidationNote, + ClassValidationError, + ForbiddenExtraKeysError, + 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 + +__all__ = ["make_dict_unstructure_fn", "make_dict_structure_fn"] + +T = TypeVar("T", bound=TypedDict) + + +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. + + :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 + req_keys = _required_keys(origin or cl) + + 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 + + # 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 + 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 + 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__] + 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: + 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 + + # 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 + 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__] + 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}'])" + 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. + 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_forbid_extra_keys: bool = False, + _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. + + :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): + base = get_origin(cl) + mapping = generate_mapping(cl, mapping) + 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"): + 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 + + # 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) + req_keys = _required_keys(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 + internal_arg_parts["__c_avn"] = AttributeValidationNote + 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 + 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): + 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} " + 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: + 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__\', []) + [__c_avn("Structuring typeddict {cl.__qualname__} @ attribute {an}", "{an}", __c_type_{an})]' + ) + lines.append(f"{i}errors.append(e)") + + 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)" + ) + 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 + 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): + 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['{an}'] = {struct_handler_name}(o['{kn}'])" + ) + else: + type_name = f"__c_type_{an}" + internal_arg_parts[type_name] = t + invocation_line = ( + f" res['{an}'] = {struct_handler_name}(o['{kn}'], {type_name})" + ) + else: + 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: + for a in non_required: + 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): + 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 override.rename is not None: + lines.append(f" res.pop('{override.rename}', None)") + + 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]) + 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_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/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_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 a7801b6a..2e95d9a1 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -4,9 +4,10 @@ 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 from ._compat import Dict_origin, List_origin @@ -264,3 +265,25 @@ 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))) + + +@pytest.mark.skipif(not is_py311_plus, reason="3.11+ only") +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 new file mode 100644 index 00000000..9bde88e7 --- /dev/null +++ b/tests/test_typeddicts.py @@ -0,0 +1,329 @@ +"""Tests for TypedDict un/structuring.""" +from datetime import datetime +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 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 ( + get_annots, + make_dict_structure_fn, + make_dict_unstructure_fn, +) + +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 + + +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_annots(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_annots(t).items() + } + return get_annots(t) + + +@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() + cls, instance = cls_and_instance + + unstructured = c.unstructure(instance, unstructure_as=cls) + + 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_annot(cls).items()): + assert unstructured is instance + + restructured = c.structure(unstructured, cls) + + assert restructured is not unstructured + assert restructured == instance + + +@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) + cls, instance = cls_and_instance + + unstructured = c.unstructure(instance, unstructure_as=cls) + + 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_annot(cls).items()): + assert unstructured is instance + + restructured = c.structure(unstructured, cls) + + assert restructured is not unstructured + assert restructured == instance + + +@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 + + 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) + unstructured = c.unstructure(instance, unstructure_as=cls) + + assert unstructured is not instance + assert unstructured == instance + + +@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 +) -> 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 + + +@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 +) -> None: + """Generic TypedDicts 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_annot(cls).items()): + assert unstructured == instance + + 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(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, + typeddict_cls=None if not is_py38 else ExtensionsTypedDict, + ), + 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, + _cattrs_detailed_validation=detailed_validation, + **{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, + _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 + + +@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, + ) + ) diff --git a/tests/test_v.py b/tests/test_v.py index 7659115e..6bfac63a 100644 --- a/tests/test_v.py +++ b/tests/test_v.py @@ -13,7 +13,7 @@ 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 @@ -225,3 +225,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" + ] diff --git a/tests/typeddicts.py b/tests/typeddicts.py new file mode 100644 index 00000000..2928e972 --- /dev/null +++ b/tests/typeddicts.py @@ -0,0 +1,240 @@ +"""Strategies for typed dicts.""" +from datetime import datetime +from string import ascii_lowercase +from typing import Any, Dict, Generic, List, Optional, Set, Tuple, TypeVar + +from attr import NOTHING +from hypothesis.strategies import ( + DrawFn, + SearchStrategy, + booleans, + composite, + datetimes, + integers, + just, + lists, + sets, + text, +) + +from cattrs._compat import ExtensionsTypedDict, NotRequired, Required, TypedDict + +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, not_required: bool = False +) -> Tuple[int, SearchStrategy, SearchStrategy]: + if total: + if not_required and draw(booleans()): + return (NotRequired[int], integers() | just(NOTHING), text(ascii_lowercase)) + else: + return int, integers(), text(ascii_lowercase) + else: + if not_required and draw(booleans()): + return Required[int], integers(), text(ascii_lowercase) + else: + return int, integers() | just(NOTHING), text(ascii_lowercase) + + +@composite +def datetime_attributes( + draw: DrawFn, total: bool = True, not_required: bool = False +) -> 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) + 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, not_required: bool = False +) -> Tuple[List[int], SearchStrategy, SearchStrategy]: + if total: + 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: + 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, + min_attrs: int = 0, + typeddict_cls: Optional[Any] = 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, not_required) + | list_of_int_attributes(total, not_required) + | datetime_attributes(total, not_required), + min_size=min_attrs, + ) + ) + + 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 + + if typeddict_cls is None: + cls = (TypedDict if draw(booleans()) else ExtensionsTypedDict)( + "HypTypedDict", attrs_dict, total=total + ) + else: + cls = typeddict_cls + + 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, typeddict_cls: Optional[Any] = None +) -> Tuple[TypedDictType, dict, Set[str]]: + """Generate TypedDicts, with the instances having extra keys.""" + 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))) + 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[tuple(generics)]] + ) + + if draw(booleans()): + + class InheritedTypedDict(cls[tuple(actual_types)]): + inherited: int + + cls = InheritedTypedDict + success_payload["inherited"] = draw(integers()) + else: + cls = cls[tuple(actual_types)] + + return (cls, success_payload) + + +def make_typeddict( + cls_name: str, attrs: Dict[str, type], total: bool = True, bases: List = [] +) -> TypedDictType: + 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(): + # 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] + + 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.