Răsfoiți Sursa

Add serializers for different var types (#1816)

Nikhil Rao 1 an în urmă
părinte
comite
1938a6cc58

+ 165 - 69
poetry.lock

@@ -1,14 +1,14 @@
-# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
 
 [[package]]
 name = "alembic"
-version = "1.11.1"
+version = "1.12.0"
 description = "A database migration tool for SQLAlchemy."
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "alembic-1.11.1-py3-none-any.whl", hash = "sha256:dc871798a601fab38332e38d6ddb38d5e734f60034baeb8e2db5b642fccd8ab8"},
-    {file = "alembic-1.11.1.tar.gz", hash = "sha256:6a810a6b012c88b33458fceb869aef09ac75d6ace5291915ba7fae44de372c01"},
+    {file = "alembic-1.12.0-py3-none-any.whl", hash = "sha256:03226222f1cf943deee6c85d9464261a6c710cd19b4fe867a3ad1f25afda610f"},
+    {file = "alembic-1.12.0.tar.gz", hash = "sha256:8e7645c32e4f200675e69f0745415335eb59a3663f5feb487abfa0b30c45888b"},
 ]
 
 [package.dependencies]
@@ -45,13 +45,13 @@ trio = ["trio (<0.22)"]
 
 [[package]]
 name = "async-timeout"
-version = "4.0.2"
+version = "4.0.3"
 description = "Timeout context manager for asyncio programs"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 files = [
-    {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
-    {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
+    {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
+    {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
 ]
 
 [package.dependencies]
@@ -230,24 +230,24 @@ pycparser = "*"
 
 [[package]]
 name = "cfgv"
-version = "3.3.1"
+version = "3.4.0"
 description = "Validate configuration and produce human readable error messages."
 optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.8"
 files = [
-    {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
-    {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
+    {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
+    {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
 ]
 
 [[package]]
 name = "click"
-version = "8.1.6"
+version = "8.1.7"
 description = "Composable command line interface toolkit"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"},
-    {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"},
+    {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+    {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
 ]
 
 [package.dependencies]
@@ -386,13 +386,13 @@ files = [
 
 [[package]]
 name = "exceptiongroup"
-version = "1.1.2"
+version = "1.1.3"
 description = "Backport of PEP 654 (exception groups)"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"},
-    {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"},
+    {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"},
+    {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"},
 ]
 
 [package.extras]
@@ -421,18 +421,19 @@ test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6
 
 [[package]]
 name = "filelock"
-version = "3.12.2"
+version = "3.12.4"
 description = "A platform independent file lock."
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
-    {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"},
-    {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"},
+    {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"},
+    {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"},
 ]
 
 [package.extras]
-docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"]
+docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"]
+typing = ["typing-extensions (>=4.7.1)"]
 
 [[package]]
 name = "greenlet"
@@ -587,13 +588,13 @@ socks = ["socksio (==1.*)"]
 
 [[package]]
 name = "identify"
-version = "2.5.26"
+version = "2.5.29"
 description = "File identification library for Python"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "identify-2.5.26-py2.py3-none-any.whl", hash = "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54"},
-    {file = "identify-2.5.26.tar.gz", hash = "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f"},
+    {file = "identify-2.5.29-py2.py3-none-any.whl", hash = "sha256:24437fbf6f4d3fe6efd0eb9d67e24dd9106db99af5ceb27996a5f7895f24bf1b"},
+    {file = "identify-2.5.29.tar.gz", hash = "sha256:d43d52b86b15918c137e3a74fff5224f60385cd0e9c38e99d07c257f02f151a5"},
 ]
 
 [package.extras]
@@ -1032,8 +1033,8 @@ files = [
 [package.dependencies]
 numpy = [
     {version = ">=1.20.3", markers = "python_version < \"3.10\""},
-    {version = ">=1.21.0", markers = "python_version >= \"3.10\""},
     {version = ">=1.23.2", markers = "python_version >= \"3.11\""},
+    {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""},
 ]
 python-dateutil = ">=2.8.1"
 pytz = ">=2020.1"
@@ -1052,6 +1053,73 @@ files = [
     {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
 ]
 
+[[package]]
+name = "pillow"
+version = "10.0.1"
+description = "Python Imaging Library (Fork)"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"},
+    {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"},
+    {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"},
+    {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"},
+    {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"},
+    {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"},
+    {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"},
+    {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"},
+    {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"},
+    {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"},
+    {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"},
+    {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"},
+    {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"},
+    {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"},
+    {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"},
+    {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"},
+    {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"},
+    {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"},
+    {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"},
+    {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"},
+    {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"},
+    {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"},
+    {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"},
+    {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"},
+    {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"},
+    {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"},
+    {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"},
+    {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"},
+    {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"},
+    {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"},
+    {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"},
+    {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"},
+    {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"},
+    {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"},
+    {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"},
+    {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"},
+    {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"},
+    {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"},
+    {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"},
+    {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"},
+    {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"},
+    {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"},
+    {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"},
+    {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"},
+    {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"},
+    {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"},
+    {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"},
+    {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"},
+    {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"},
+    {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"},
+    {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"},
+    {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"},
+    {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"},
+    {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"},
+]
+
+[package.extras]
+docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"]
+tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
+
 [[package]]
 name = "platformdirs"
 version = "3.10.0"
@@ -1072,13 +1140,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
 
 [[package]]
 name = "plotly"
-version = "5.15.0"
+version = "5.17.0"
 description = "An open-source, interactive data visualization library for Python"
 optional = false
 python-versions = ">=3.6"
 files = [
-    {file = "plotly-5.15.0-py2.py3-none-any.whl", hash = "sha256:3508876bbd6aefb8a692c21a7128ca87ce42498dd041efa5c933ee44b55aab24"},
-    {file = "plotly-5.15.0.tar.gz", hash = "sha256:822eabe53997d5ebf23c77e1d1fcbf3bb6aa745eb05d532afd4b6f9a2e2ab02f"},
+    {file = "plotly-5.17.0-py2.py3-none-any.whl", hash = "sha256:7c84cdf11da162423da957bb093287134f2d6f170eb9a74f1459f825892247c3"},
+    {file = "plotly-5.17.0.tar.gz", hash = "sha256:290d796bf7bab87aad184fe24b86096234c4c95dcca6ecbca02d02bdf17d3d97"},
 ]
 
 [package.dependencies]
@@ -1105,13 +1173,13 @@ testing = ["pytest", "pytest-benchmark"]
 
 [[package]]
 name = "pre-commit"
-version = "3.3.3"
+version = "3.4.0"
 description = "A framework for managing and maintaining multi-language pre-commit hooks."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"},
-    {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"},
+    {file = "pre_commit-3.4.0-py2.py3-none-any.whl", hash = "sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945"},
+    {file = "pre_commit-3.4.0.tar.gz", hash = "sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522"},
 ]
 
 [package.dependencies]
@@ -1212,13 +1280,13 @@ email = ["email-validator (>=1.0.3)"]
 
 [[package]]
 name = "pygments"
-version = "2.15.1"
+version = "2.16.1"
 description = "Pygments is a syntax highlighting package written in Python."
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"},
-    {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"},
+    {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"},
+    {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"},
 ]
 
 [package.extras]
@@ -1226,13 +1294,13 @@ plugins = ["importlib-metadata"]
 
 [[package]]
 name = "pyright"
-version = "1.1.318"
+version = "1.1.327"
 description = "Command line wrapper for pyright"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pyright-1.1.318-py3-none-any.whl", hash = "sha256:056c1b2e711c3526e32919de1684ae599d34b7ec27e94398858a43f56ac9ba9b"},
-    {file = "pyright-1.1.318.tar.gz", hash = "sha256:69dcf9c32d5be27d531750de627e76a7cadc741d333b547c09044278b508db7b"},
+    {file = "pyright-1.1.327-py3-none-any.whl", hash = "sha256:3462cda239e9140276238bbdbd0b59d77406f1c2e14d8cb8c20c8e25639c6b3c"},
+    {file = "pyright-1.1.327.tar.gz", hash = "sha256:ba74148ad64f22020dbbed6781c4bdb38ecb8a7ca90dc3c87a4f08d1c0e11592"},
 ]
 
 [package.dependencies]
@@ -1257,13 +1325,13 @@ files = [
 
 [[package]]
 name = "pytest"
-version = "7.4.0"
+version = "7.4.2"
 description = "pytest: simple powerful testing with Python"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"},
-    {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"},
+    {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"},
+    {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"},
 ]
 
 [package.dependencies]
@@ -1348,15 +1416,18 @@ six = ">=1.5"
 
 [[package]]
 name = "python-engineio"
-version = "4.5.1"
+version = "4.7.1"
 description = "Engine.IO server and client for Python"
 optional = false
 python-versions = ">=3.6"
 files = [
-    {file = "python-engineio-4.5.1.tar.gz", hash = "sha256:b167a1b208fcdce5dbe96a61a6ca22391cfa6715d796c22de93e3adf9c07ae0c"},
-    {file = "python_engineio-4.5.1-py3-none-any.whl", hash = "sha256:67a675569f3e9bb274a8077f3c2068a8fe79cbfcb111cf31ca27b968484fe6c7"},
+    {file = "python-engineio-4.7.1.tar.gz", hash = "sha256:a8422e345cd9a21451303380b160742ff02197975b1c3a02cef115febe2b1b20"},
+    {file = "python_engineio-4.7.1-py3-none-any.whl", hash = "sha256:52499e8ab94fea1a6525ffe872fe7028d04b575799c5fa8e2cf7880e032de42e"},
 ]
 
+[package.dependencies]
+simple-websocket = ">=0.10.0"
+
 [package.extras]
 asyncio-client = ["aiohttp (>=3.4)"]
 client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
@@ -1377,32 +1448,33 @@ six = ">=1.4.0"
 
 [[package]]
 name = "python-socketio"
-version = "5.8.0"
+version = "5.9.0"
 description = "Socket.IO server and client for Python"
 optional = false
 python-versions = ">=3.6"
 files = [
-    {file = "python-socketio-5.8.0.tar.gz", hash = "sha256:e714f4dddfaaa0cb0e37a1e2deef2bb60590a5b9fea9c343dd8ca5e688416fd9"},
-    {file = "python_socketio-5.8.0-py3-none-any.whl", hash = "sha256:7adb8867aac1c2929b9c1429f1c02e12ca4c36b67c807967393e367dfbb01441"},
+    {file = "python-socketio-5.9.0.tar.gz", hash = "sha256:dc42735f65534187f381fde291ebf620216a4960001370f32de940229b2e7f8f"},
+    {file = "python_socketio-5.9.0-py3-none-any.whl", hash = "sha256:c20f12e4ed0cba57581af26bbeea9998bc2eeebb3b952fa92493a1e051cfe9dc"},
 ]
 
 [package.dependencies]
 bidict = ">=0.21.0"
-python-engineio = ">=4.3.0"
+python-engineio = ">=4.7.0"
 
 [package.extras]
 asyncio-client = ["aiohttp (>=3.4)"]
 client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
+docs = ["sphinx"]
 
 [[package]]
 name = "pytz"
-version = "2023.3"
+version = "2023.3.post1"
 description = "World timezone definitions, modern and historical"
 optional = false
 python-versions = "*"
 files = [
-    {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"},
-    {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"},
+    {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"},
+    {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"},
 ]
 
 [[package]]
@@ -1417,6 +1489,7 @@ files = [
     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
+    {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
     {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
     {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
     {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@@ -1424,8 +1497,15 @@ files = [
     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
+    {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
     {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
     {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
+    {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
+    {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+    {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
+    {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
+    {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
+    {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
     {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
     {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
     {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@@ -1442,6 +1522,7 @@ files = [
     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
+    {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
     {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
     {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
     {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@@ -1449,6 +1530,7 @@ files = [
     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
+    {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
     {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
     {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
     {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
@@ -1476,13 +1558,13 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"
 
 [[package]]
 name = "rich"
-version = "13.5.1"
+version = "13.5.2"
 description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
 optional = false
 python-versions = ">=3.7.0"
 files = [
-    {file = "rich-13.5.1-py3-none-any.whl", hash = "sha256:b97381b204a206e1be618f5e1215a57174a1a7732490b3bf6668cf41d30bc72d"},
-    {file = "rich-13.5.1.tar.gz", hash = "sha256:881653ee7037803559d8eae98f145e0a4c4b0ec3ff0300d2cc8d479c71fc6819"},
+    {file = "rich-13.5.2-py3-none-any.whl", hash = "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"},
+    {file = "rich-13.5.2.tar.gz", hash = "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"},
 ]
 
 [package.dependencies]
@@ -1551,6 +1633,20 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-g
 testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
 testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
 
+[[package]]
+name = "simple-websocket"
+version = "0.10.1"
+description = "Simple WebSocket server and client for Python"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "simple-websocket-0.10.1.tar.gz", hash = "sha256:0ab46c8ffa51a46dc95eed94608b3b722841c0bf849def71d465c5c356679c82"},
+    {file = "simple_websocket-0.10.1-py3-none-any.whl", hash = "sha256:62c36bacfd75cc867927bb39d91951342a7234bdfe20f41dd969a3b8bb1413b7"},
+]
+
+[package.dependencies]
+wsproto = "*"
+
 [[package]]
 name = "six"
 version = "1.16.0"
@@ -1635,7 +1731,7 @@ files = [
 ]
 
 [package.dependencies]
-greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\")"}
+greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
 importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
 
 [package.extras]
@@ -1731,13 +1827,13 @@ test = ["aiomysql (>=0.1.1,<0.2.0)", "aiosqlite (>=0.17.0,<0.20.0)", "arrow (>=1
 
 [[package]]
 name = "tenacity"
-version = "8.2.2"
+version = "8.2.3"
 description = "Retry code until it succeeds"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 files = [
-    {file = "tenacity-8.2.2-py3-none-any.whl", hash = "sha256:2f277afb21b851637e8f52e6a613ff08734c347dc19ade928e519d7d2d8569b0"},
-    {file = "tenacity-8.2.2.tar.gz", hash = "sha256:43af037822bd0029025877f3b2d97cc4d7bb0c2991000a3d59d71517c5c969e0"},
+    {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"},
+    {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"},
 ]
 
 [package.extras]
@@ -1787,13 +1883,13 @@ sortedcontainers = "*"
 
 [[package]]
 name = "trio-websocket"
-version = "0.10.3"
+version = "0.10.4"
 description = "WebSocket library for Trio"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "trio-websocket-0.10.3.tar.gz", hash = "sha256:1a748604ad906a7dcab9a43c6eb5681e37de4793ba0847ef0bc9486933ed027b"},
-    {file = "trio_websocket-0.10.3-py3-none-any.whl", hash = "sha256:a9937d48e8132ebf833019efde2a52ca82d223a30a7ea3e8d60a7d28f75a4e3a"},
+    {file = "trio-websocket-0.10.4.tar.gz", hash = "sha256:e66b3db3e2453017431dfbd352081006654e1241c2a6800dc2f43d7df54d55c5"},
+    {file = "trio_websocket-0.10.4-py3-none-any.whl", hash = "sha256:c7a620c4013c34b7e4477d89fe76695da1e455e4510a8d7ae13f81c632bdce1d"},
 ]
 
 [package.dependencies]
@@ -1923,13 +2019,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
 
 [[package]]
 name = "virtualenv"
-version = "20.24.2"
+version = "20.24.5"
 description = "Virtual Python Environment builder"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "virtualenv-20.24.2-py3-none-any.whl", hash = "sha256:43a3052be36080548bdee0b42919c88072037d50d56c28bd3f853cbe92b953ff"},
-    {file = "virtualenv-20.24.2.tar.gz", hash = "sha256:fd8a78f46f6b99a67b7ec5cf73f92357891a7b3a40fd97637c27f854aae3b9e0"},
+    {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"},
+    {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"},
 ]
 
 [package.dependencies]
@@ -1938,7 +2034,7 @@ filelock = ">=3.12.2,<4"
 platformdirs = ">=3.9.1,<4"
 
 [package.extras]
-docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
+docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
 test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
 
 [[package]]
@@ -2125,4 +2221,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.7"
-content-hash = "44cce3d4423be203bf6b1ddc046cbdd9061924523b86baea8a42cd954dc86b36"
+content-hash = "0dd6230851cc4f43e192e45431d1c1dcb451b7946ae7cd169e220e7f7a072aa2"

+ 4 - 1
pyproject.toml

@@ -29,7 +29,6 @@ fastapi = "^0.96.0"
 gunicorn = "^20.1.0"
 httpx = "^0.24.0"
 jinja2 = "^3.1.2"
-plotly = "^5.13.0"
 psutil = "^5.9.4"
 pydantic = "^1.10.2"
 python-multipart = "^0.0.5"
@@ -63,6 +62,10 @@ pandas = [
     {version = "^1.5.3", python = ">=3.8,<4.0"},
     {version = "^1.1", python = ">=3.7, <3.8"}
 ]
+pillow = [
+    {version = "^10.0.0", python = ">=3.8,<4.0"}
+]
+plotly = "^5.13.0"
 asynctest = "^0.13.0"
 pre-commit = {version = "^3.2.1", python = ">=3.8,<4.0"}
 selenium = "^4.11.0"

+ 56 - 17
reflex/components/datadisplay/datatable.py

@@ -1,10 +1,13 @@
 """Table components."""
 
+from __future__ import annotations
+
 from typing import Any, Dict, List, Union
 
 from reflex.components.component import Component
 from reflex.components.tags import Tag
-from reflex.utils import format, imports, types
+from reflex.utils import imports, types
+from reflex.utils.serializers import serialize, serializer
 from reflex.vars import BaseVar, ComputedVar, ImportVar, Var
 
 
@@ -106,23 +109,59 @@ class DataTable(Gridjs):
         )
 
     def _render(self) -> Tag:
-        if isinstance(self.data, Var):
-            if types.is_dataframe(self.data.type_):
-                self.columns = BaseVar(
-                    name=f"{self.data.name}.columns",
-                    type_=List[Any],
-                    state=self.data.state,
-                )
-                self.data = BaseVar(
-                    name=f"{self.data.name}.data",
-                    type_=List[List[Any]],
-                    state=self.data.state,
-                )
-        else:
+        if isinstance(self.data, Var) and types.is_dataframe(self.data.type_):
+            self.columns = BaseVar(
+                name=f"{self.data.name}.columns",
+                type_=List[Any],
+                state=self.data.state,
+            )
+            self.data = BaseVar(
+                name=f"{self.data.name}.data",
+                type_=List[List[Any]],
+                state=self.data.state,
+            )
+        if types.is_dataframe(type(self.data)):
             # If given a pandas df break up the data and columns
-            if types.is_dataframe(type(self.data)):
-                self.columns = Var.create(list(self.data.columns.values.tolist()))  # type: ignore
-                self.data = Var.create(format.format_dataframe_values(self.data))  # type: ignore
+            data = serialize(self.data)
+            assert isinstance(data, dict), "Serialized dataframe should be a dict."
+            self.columns = Var.create_safe(data["columns"])
+            self.data = Var.create_safe(data["data"])
 
         # Render the table.
         return super()._render()
+
+
+try:
+    from pandas import DataFrame
+
+    def format_dataframe_values(df: DataFrame) -> List[List[Any]]:
+        """Format dataframe values to a list of lists.
+
+        Args:
+            df: The dataframe to format.
+
+        Returns:
+            The dataframe as a list of lists.
+        """
+        return [
+            [str(d) if isinstance(d, (list, tuple)) else d for d in data]
+            for data in list(df.values.tolist())
+        ]
+
+    @serializer
+    def serialize_dataframe(df: DataFrame) -> dict:
+        """Serialize a pandas dataframe.
+
+        Args:
+            df: The dataframe to serialize.
+
+        Returns:
+            The serialized dataframe.
+        """
+        return {
+            "columns": df.columns.tolist(),
+            "data": format_dataframe_values(df),
+        }
+
+except ImportError:
+    pass

+ 1 - 0
reflex/components/disclosure/tabs.pyi

@@ -25,6 +25,7 @@ class Tabs(ChakraComponent):
             is_manual: If true, the tabs will be manually activated and display its panel by pressing Space or Enter. If false, the tabs will be automatically activated and their panel is displayed when they receive focus.
             orientation: The orientation of the tab list.
             variant: "line" | "enclosed" | "enclosed-colored" | "soft-rounded" | "solid-rounded" | "unstyled"
+            color_scheme: The color scheme of the tabs.
             items: The items for the tabs component, a list of tuple (label, panel)
             **props: The properties of the component.
 

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
reflex/components/forms/select.pyi


+ 27 - 15
reflex/components/graphing/plotly.py

@@ -1,13 +1,17 @@
 """Component for displaying a plotly graph."""
 
-from typing import Dict, List
-
-from plotly.graph_objects import Figure
+import json
+from typing import Any, Dict, List
 
 from reflex.components.component import NoSSRComponent
-from reflex.components.tags import Tag
+from reflex.utils.serializers import serializer
 from reflex.vars import Var
 
+try:
+    from plotly.graph_objects import Figure
+except ImportError:
+    Figure = Any
+
 
 class PlotlyLib(NoSSRComponent):
     """A component that wraps a plotly lib."""
@@ -39,14 +43,22 @@ class Plotly(PlotlyLib):
     # If true, the graph will resize when the window is resized.
     use_resize_handler: Var[bool]
 
-    def _render(self) -> Tag:
-        if (
-            isinstance(self.data, Figure)
-            and self.layout is None
-            and self.width is not None
-        ):
-            layout = Var.create({"width": self.width, "height": self.height})
-            assert layout is not None
-            self.layout = layout
-
-        return super()._render()
+
+try:
+    from plotly.graph_objects import Figure
+    from plotly.io import to_json
+
+    @serializer
+    def serialize_figure(figure: Figure) -> list:
+        """Serialize a plotly figure.
+
+        Args:
+            figure: The figure to serialize.
+
+        Returns:
+            The serialized figure.
+        """
+        return json.loads(str(to_json(figure)))["data"]
+
+except ImportError:
+    pass

+ 2 - 2
reflex/components/graphing/plotly.pyi

@@ -3,7 +3,7 @@
 # This file was generated by `scripts/pyi_generator.py`!
 # ------------------------------------------------------
 
-from typing import Dict, List, Optional, Union, overload
+from typing import Any, Dict, List, Optional, Union, overload
 from reflex.components.component import Component
 from reflex.components.component import NoSSRComponent
 from reflex.vars import Var, BaseVar, ComputedVar
@@ -31,7 +31,7 @@ class PlotlyLib(NoSSRComponent):
 class Plotly(PlotlyLib):
     @overload
     @classmethod
-    def create(cls, *children, data: Optional[Union[Var[Figure], Figure]] = None, layout: Optional[Union[Var[Dict], Dict]] = None, width: Optional[Union[Var[str], str]] = None, height: Optional[Union[Var[str], str]] = None, use_resize_handler: Optional[Union[Var[bool], bool]] = None, on_blur: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_click: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_context_menu: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_double_click: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_focus: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mount: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_down: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_enter: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_leave: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_move: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_out: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_over: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_up: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_scroll: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_unmount: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, **props) -> "Plotly":  # type: ignore
+    def create(cls, *children, data: Optional[Union[Var[Any], Any]] = None, layout: Optional[Union[Var[Dict], Dict]] = None, width: Optional[Union[Var[str], str]] = None, height: Optional[Union[Var[str], str]] = None, use_resize_handler: Optional[Union[Var[bool], bool]] = None, on_blur: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_click: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_context_menu: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_double_click: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_focus: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mount: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_down: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_enter: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_leave: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_move: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_out: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_over: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_mouse_up: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_scroll: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, on_unmount: Optional[Union[EventHandler, EventSpec, List, function, BaseVar]] = None, **props) -> "Plotly":  # type: ignore
         """Create the component.
 
         Args:

+ 29 - 6
reflex/components/media/image.py

@@ -1,12 +1,14 @@
 """An image component."""
 from __future__ import annotations
 
-from typing import Any, Optional, Set
+import base64
+import io
+from typing import Any, Optional
 
 from reflex.components.component import Component
 from reflex.components.libs.chakra import ChakraComponent
 from reflex.components.tags import Tag
-from reflex.utils import format, types
+from reflex.utils.serializers import serializer
 from reflex.vars import Var
 
 
@@ -51,7 +53,7 @@ class Image(ChakraComponent):
     # Learn more _[here](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)_
     src_set: Var[str]
 
-    def get_triggers(self) -> Set[str]:
+    def get_triggers(self) -> set[str]:
         """Get the event triggers for the component.
 
         Returns:
@@ -60,9 +62,30 @@ class Image(ChakraComponent):
         return super().get_triggers() | {"on_error", "on_load"}
 
     def _render(self) -> Tag:
-        # If the src is an image, convert it to a base64 string.
-        if types.is_image(type(self.src)):
-            self.src = Var.create(format.format_image_data(self.src))  # type: ignore
+        self.src.is_string = True
 
         # Render the table.
         return super()._render()
+
+
+try:
+    from PIL.Image import Image as Img
+
+    @serializer
+    def serialize_image(image: Img) -> str:
+        """Serialize a plotly figure.
+
+        Args:
+            image: The image to serialize.
+
+        Returns:
+            The serialized image.
+        """
+        buff = io.BytesIO()
+        image.save(buff, format="PNG")
+        image_bytes = buff.getvalue()
+        base64_image = base64.b64encode(image_bytes).decode("utf-8")
+        return f"data:image/png;base64,{base64_image}"
+
+except ImportError:
+    pass

+ 1 - 1
reflex/components/media/image.pyi

@@ -3,7 +3,7 @@
 # This file was generated by `scripts/pyi_generator.py`!
 # ------------------------------------------------------
 
-from typing import Any, Optional, Set, Union, overload
+from typing import Any, Optional, Union, overload
 from reflex.components.libs.chakra import ChakraComponent
 from reflex.components.component import Component
 from reflex.vars import Var, BaseVar, ComputedVar

+ 1 - 0
reflex/model.py

@@ -197,6 +197,7 @@ class Model(Base, sqlmodel.SQLModel):
                 target_metadata=sqlmodel.SQLModel.metadata,
                 render_item=cls._alembic_render_item,
                 process_revision_directives=writer,  # type: ignore
+                compare_type=False,
             )
             env.run_migrations()
         changes_detected = False

+ 10 - 127
reflex/utils/format.py

@@ -2,22 +2,16 @@
 
 from __future__ import annotations
 
-import base64
-import io
 import json
 import os
 import os.path as op
 import re
 import sys
-import types as builtin_types
-from typing import TYPE_CHECKING, Any, Callable, Type, Union
-
-import plotly.graph_objects as go
-from plotly.graph_objects import Figure
-from plotly.io import to_json
+from typing import TYPE_CHECKING, Any, Union
 
 from reflex import constants
-from reflex.utils import exceptions, types
+from reflex.utils import exceptions, serializers, types
+from reflex.utils.serializers import serialize
 from reflex.vars import Var
 
 if TYPE_CHECKING:
@@ -316,12 +310,9 @@ def format_prop(
                 return prop
             return json_dumps(prop)
 
-        elif isinstance(prop, Figure):
-            prop = json.loads(to_json(prop))["data"]  # type: ignore
-
         # For dictionaries, convert any properties to strings.
         elif isinstance(prop, dict):
-            prop = format_dict(prop)
+            prop = serializers.serialize_dict(prop)  # type: ignore
 
         else:
             # Dump the prop as JSON.
@@ -461,44 +452,6 @@ def format_query_params(router_data: dict[str, Any]) -> dict[str, str]:
     return {k.replace("-", "_"): v for k, v in params.items()}
 
 
-def format_dataframe_values(value: Type) -> list[Any]:
-    """Format dataframe values.
-
-    Args:
-        value: The value to format.
-
-    Returns:
-        Format data
-    """
-    if not types.is_dataframe(type(value)):
-        return value
-
-    format_data = []
-    for data in list(value.values.tolist()):
-        element = []
-        for d in data:
-            element.append(str(d) if isinstance(d, (list, tuple)) else d)
-        format_data.append(element)
-
-    return format_data
-
-
-def format_image_data(value: Type) -> str:
-    """Format image data.
-
-    Args:
-        value: The value to format.
-
-    Returns:
-        Format data
-    """
-    buff = io.BytesIO()
-    value.save(buff, format="PNG")
-    image_bytes = buff.getvalue()
-    base64_image = base64.b64encode(image_bytes).decode("utf-8")
-    return f"data:image/png;base64,{base64_image}"
-
-
 def format_state(value: Any) -> Any:
     """Recursively format values in the given state.
 
@@ -523,30 +476,12 @@ def format_state(value: Any) -> Any:
     if isinstance(value, types.StateBases):
         return value
 
-    # Convert plotly figures to JSON.
-    if isinstance(value, go.Figure):
-        return json.loads(to_json(value))["data"]  # type: ignore
-
-    # Convert pandas dataframes to JSON.
-    if types.is_dataframe(type(value)):
-        return {
-            "columns": value.columns.tolist(),
-            "data": format_dataframe_values(value),
-        }
-
-    # Convert datetime objects to str.
-    if types.is_datetime(type(value)):
-        return str(value)
-
-    # Convert Image objects to base64.
-    if types.is_image(type(value)):
-        return format_image_data(value)  # type: ignore
-
-    raise TypeError(
-        "State vars must be primitive Python types, "
-        "or subclasses of rx.Base. "
-        f"Got var of type {type(value)}."
-    )
+    # Serialize the value.
+    serialized = serialize(value)
+    if serialized is not None:
+        return serialized
+
+    raise TypeError(f"No JSON serializer found for var {value} of type {type(value)}.")
 
 
 def format_ref(ref: str) -> str:
@@ -580,58 +515,6 @@ def format_array_ref(refs: str, idx: Var | None) -> str:
     return f"refs_{clean_ref}"
 
 
-def format_dict(prop: ComponentStyle) -> str:
-    """Format a dict with vars potentially as values.
-
-    Args:
-        prop: The dict to format.
-
-    Returns:
-        The formatted dict.
-
-    Raises:
-        InvalidStylePropError: If a style prop has a callable value
-    """
-    # Import here to avoid circular imports.
-    from reflex.event import EventHandler
-    from reflex.vars import Var
-
-    prop_dict = {}
-
-    # Convert any var keys to strings.
-    for key, value in prop.items():
-        if issubclass(type(value), Callable):
-            raise exceptions.InvalidStylePropError(
-                f"The style prop `{to_snake_case(key)}` cannot have "  # type: ignore
-                f"`{value.fn.__qualname__ if isinstance(value, EventHandler) else value.__qualname__ if isinstance(value, builtin_types.FunctionType) else value}`, "
-                f"an event handler or callable as its value"
-            )
-        prop_dict[key] = str(value) if isinstance(value, Var) else value
-
-    # Dump the dict to a string.
-    fprop = json_dumps(prop_dict)
-
-    def unescape_double_quotes_in_var(m: re.Match) -> str:
-        # Since the outer quotes are removed, the inner escaped quotes must be unescaped.
-        return re.sub('\\\\"', '"', m.group(1))
-
-    # This substitution is necessary to unwrap var values.
-    fprop = re.sub(
-        pattern=r"""
-            (?<!\\)      # must NOT start with a backslash
-            "            # match opening double quote of JSON value
-            {(.*?)}      # extract the value between curly braces (non-greedy)
-            "            # match must end with an unescaped double quote
-        """,
-        repl=unescape_double_quotes_in_var,
-        string=fprop,
-        flags=re.VERBOSE,
-    )
-
-    # Return the formatted dict.
-    return fprop
-
-
 def format_breadcrumbs(route: str) -> list[tuple[str, str]]:
     """Take a route and return a list of tuple for use in breadcrumb.
 

+ 193 - 0
reflex/utils/serializers.py

@@ -0,0 +1,193 @@
+"""Serializers used to convert Var types to JSON strings."""
+
+from __future__ import annotations
+
+import re
+import types as builtin_types
+from datetime import date, datetime, time, timedelta
+from typing import Any, Callable, Dict, Type, Union, get_type_hints
+
+from reflex.utils import exceptions, types
+
+# Mapping from type to a serializer.
+# The serializer should convert the type to a JSON object.
+SerializedType = Union[str, bool, int, float, list, dict]
+Serializer = Callable[[Type], SerializedType]
+SERIALIZERS: dict[Type, Serializer] = {}
+
+
+def serializer(fn: Serializer) -> Serializer:
+    """Decorator to add a serializer for a given type.
+
+    Args:
+        fn: The function to decorate.
+
+    Returns:
+        The decorated function.
+
+    Raises:
+        ValueError: If the function does not take a single argument.
+    """
+    # Get the global serializers.
+    global SERIALIZERS
+
+    # Check the type hints to get the type of the argument.
+    type_hints = get_type_hints(fn)
+    args = [arg for arg in type_hints if arg != "return"]
+
+    # Make sure the function takes a single argument.
+    if len(args) != 1:
+        raise ValueError("Serializer must take a single argument.")
+
+    # Get the type of the argument.
+    type_ = type_hints[args[0]]
+
+    # Make sure the type is not already registered.
+    registered_fn = SERIALIZERS.get(type_)
+    if registered_fn is not None and registered_fn != fn:
+        raise ValueError(
+            f"Serializer for type {type_} is already registered as {registered_fn.__qualname__}."
+        )
+
+    # Register the serializer.
+    SERIALIZERS[type_] = fn
+
+    # Return the function.
+    return fn
+
+
+def serialize(value: Any) -> SerializedType | None:
+    """Serialize the value to a JSON string.
+
+    Args:
+        value: The value to serialize.
+
+    Returns:
+        The serialized value, or None if a serializer is not found.
+    """
+    # Get the serializer for the type.
+    serializer = get_serializer(type(value))
+
+    # If there is no serializer, return None.
+    if serializer is None:
+        return None
+
+    # Serialize the value.
+    return serializer(value)
+
+
+def get_serializer(type_: Type) -> Serializer | None:
+    """Get the serializer for the type.
+
+    Args:
+        type_: The type to get the serializer for.
+
+    Returns:
+        The serializer for the type, or None if there is no serializer.
+    """
+    global SERIALIZERS
+
+    # First, check if the type is registered.
+    serializer = SERIALIZERS.get(type_)
+    if serializer is not None:
+        return serializer
+
+    # If the type is not registered, check if it is a subclass of a registered type.
+    for registered_type, serializer in SERIALIZERS.items():
+        if types._issubclass(type_, registered_type):
+            return serializer
+
+    # If there is no serializer, return None.
+    return None
+
+
+def has_serializer(type_: Type) -> bool:
+    """Check if there is a serializer for the type.
+
+    Args:
+        type_: The type to check.
+
+    Returns:
+        Whether there is a serializer for the type.
+    """
+    return get_serializer(type_) is not None
+
+
+@serializer
+def serialize_str(value: str) -> str:
+    """Serialize a string.
+
+    Args:
+        value: The string to serialize.
+
+    Returns:
+        The serialized string.
+    """
+    return value
+
+
+@serializer
+def serialize_dict(prop: Dict[str, Any]) -> str:
+    """Serialize a dictionary to a JSON string.
+
+    Args:
+        prop: The dictionary to serialize.
+
+    Returns:
+        The serialized dictionary.
+
+    Raises:
+        InvalidStylePropError: If the style prop is invalid.
+    """
+    # Import here to avoid circular imports.
+    from reflex.event import EventHandler
+    from reflex.utils.format import json_dumps, to_snake_case
+    from reflex.vars import Var
+
+    prop_dict = {}
+
+    # Convert any var keys to strings.
+    for key, value in prop.items():
+        if types._issubclass(type(value), Callable):
+            raise exceptions.InvalidStylePropError(
+                f"The style prop `{to_snake_case(key)}` cannot have "  # type: ignore
+                f"`{value.fn.__qualname__ if isinstance(value, EventHandler) else value.__qualname__ if isinstance(value, builtin_types.FunctionType) else value}`, "
+                f"an event handler or callable as its value"
+            )
+        prop_dict[key] = str(value) if isinstance(value, Var) else value
+
+    # Dump the dict to a string.
+    fprop = json_dumps(prop_dict)
+
+    def unescape_double_quotes_in_var(m: re.Match) -> str:
+        # Since the outer quotes are removed, the inner escaped quotes must be unescaped.
+        return re.sub('\\\\"', '"', m.group(1))
+
+    # This substitution is necessary to unwrap var values.
+    fprop = re.sub(
+        pattern=r"""
+            (?<!\\)      # must NOT start with a backslash
+            "            # match opening double quote of JSON value
+            {(.*?)}      # extract the value between curly braces (non-greedy)
+            "            # match must end with an unescaped double quote
+        """,
+        repl=unescape_double_quotes_in_var,
+        string=fprop,
+        flags=re.VERBOSE,
+    )
+
+    # Return the formatted dict.
+    return fprop
+
+
+@serializer
+def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
+    """Serialize a datetime to a JSON string.
+
+    Args:
+        dt: The datetime to serialize.
+
+    Returns:
+        The serialized datetime.
+    """
+    return str(dt)

+ 6 - 50
reflex/utils/types.py

@@ -4,10 +4,10 @@ from __future__ import annotations
 
 import contextlib
 import typing
-from datetime import date, datetime, time, timedelta
 from typing import Any, Callable, Type, Union, _GenericAlias  # type: ignore
 
 from reflex.base import Base
+from reflex.utils import serializers
 
 # Union of generic types.
 GenericType = Union[Type, _GenericAlias]
@@ -143,60 +143,16 @@ def is_dataframe(value: Type) -> bool:
     return value.__name__ == "DataFrame"
 
 
-def is_image(value: Type) -> bool:
-    """Check if the given value is a pillow image. By checking if the value subclasses PIL.
+def is_valid_var_type(type_: Type) -> bool:
+    """Check if the given type is a valid prop type.
 
     Args:
-        value: The value to check.
-
-    Returns:
-        Whether the value is a pillow image.
-    """
-    if is_generic_alias(value) or value == typing.Any:
-        return False
-    return "PIL" in value.__module__
-
-
-def is_figure(value: Type) -> bool:
-    """Check if the given value is a figure.
-
-    Args:
-        value: The value to check.
-
-    Returns:
-        Whether the value is a figure.
-    """
-    return value.__name__ == "Figure"
-
-
-def is_datetime(value: Type) -> bool:
-    """Check if the given value is a datetime object.
-
-    Args:
-        value: The value to check.
-
-    Returns:
-        Whether the value is a date, datetime, time, or timedelta.
-    """
-    return issubclass(value, (date, datetime, time, timedelta))
-
-
-def is_valid_var_type(var: Type) -> bool:
-    """Check if the given value is a valid prop type.
-
-    Args:
-        var: The value to check.
+        type_: The type to check.
 
     Returns:
-        Whether the value is a valid prop type.
+        Whether the type is a valid prop type.
     """
-    return (
-        _issubclass(var, StateVar)
-        or is_dataframe(var)
-        or is_figure(var)
-        or is_image(var)
-        or is_datetime(var)
-    )
+    return _issubclass(type_, StateVar) or serializers.has_serializer(type_)
 
 
 def is_backend_variable(name: str) -> bool:

+ 7 - 11
reflex/vars.py

@@ -24,13 +24,12 @@ from typing import (
     get_type_hints,
 )
 
-from plotly.graph_objects import Figure
-from plotly.io import to_json
 from pydantic.fields import ModelField
 
 from reflex import constants
 from reflex.base import Base
 from reflex.utils import console, format, types
+from reflex.utils.serializers import serialize
 
 if TYPE_CHECKING:
     from reflex.state import State
@@ -126,19 +125,16 @@ class Var(ABC):
 
         type_ = type(value)
 
-        # Special case for plotly figures.
-        if isinstance(value, Figure):
-            value = json.loads(to_json(value))["data"]  # type: ignore
-            type_ = Figure
-
-        if isinstance(value, dict):
-            value = format.format_dict(value)
+        # Try to serialize the value.
+        serialized = serialize(value)
+        if serialized is not None:
+            value = serialized
 
         try:
             name = value if isinstance(value, str) else json.dumps(value)
         except TypeError as e:
             raise TypeError(
-                f"To create a Var must be Var or JSON-serializable. Got {value} of type {type(value)}."
+                f"No JSON serializer found for var {value} of type {type_}."
             ) from e
 
         return BaseVar(name=name, type_=type_, is_local=is_local, is_string=is_string)
@@ -184,7 +180,7 @@ class Var(ABC):
         """
         if self.state:
             return self.full_name
-        if self.is_string or self.type_ is Figure:
+        if self.is_string:
             return self.name
         try:
             return json.loads(self.name)

+ 9 - 2
scripts/pyi_generator.py

@@ -7,7 +7,7 @@ import re
 import sys
 from inspect import getfullargspec
 from pathlib import Path
-from typing import Any, Dict, List, Optional, get_args
+from typing import Any, Dict, List, Optional, Union, get_args  # NOQA
 
 import black
 
@@ -181,7 +181,14 @@ class PyiGenerator:
         return _get_var_definition(self.current_module, _name)
 
     def _generate_function(self, _name, _func):
-        definition = "".join(inspect.getsource(_func).split(":\n")[0].split("\n"))
+        import textwrap
+
+        # Don't generate indented functions.
+        source = inspect.getsource(_func)
+        if textwrap.dedent(source) != source:
+            return []
+
+        definition = "".join([line for line in source.split(":\n")[0].split("\n")])
         return [f"{definition}:", "    ..."]
 
     def _write_pyi_file(self, variables, functions, classes):

+ 21 - 6
tests/components/datadisplay/test_datatable.py

@@ -2,8 +2,12 @@ import pandas as pd
 import pytest
 
 import reflex as rx
-from reflex.components import data_table
+from reflex.components.datadisplay.datatable import (
+    DataTable,
+    serialize_dataframe,  # type: ignore
+)
 from reflex.utils import types
+from reflex.utils.serializers import serialize
 
 
 @pytest.mark.parametrize(
@@ -31,11 +35,11 @@ def test_validate_data_table(data_table_state: rx.Var, expected):
 
     """
     if not types.is_dataframe(data_table_state.data.type_):
-        data_table_component = data_table(
+        data_table_component = DataTable.create(
             data=data_table_state.data, columns=data_table_state.columns
         )
     else:
-        data_table_component = data_table(data=data_table_state.data)
+        data_table_component = DataTable.create(data=data_table_state.data)
 
     data_table_dict = data_table_component.render()
 
@@ -62,7 +66,7 @@ def test_invalid_props(props):
         props: props to pass in component.
     """
     with pytest.raises(ValueError):
-        data_table(**props)
+        DataTable.create(**props)
 
 
 @pytest.mark.parametrize(
@@ -96,10 +100,21 @@ def test_computed_var_without_annotation(fixture, request, err_msg, is_data_fram
     """
     with pytest.raises(ValueError) as err:
         if is_data_frame:
-            data_table(data=request.getfixturevalue(fixture).data)
+            DataTable.create(data=request.getfixturevalue(fixture).data)
         else:
-            data_table(
+            DataTable.create(
                 data=request.getfixturevalue(fixture).data,
                 columns=request.getfixturevalue(fixture).columns,
             )
     assert err.value.args[0] == err_msg
+
+
+def test_serialize_dataframe():
+    """Test if dataframe is serialized correctly."""
+    df = pd.DataFrame(
+        [["foo", "bar"], ["foo1", "bar1"]], columns=["column1", "column2"]
+    )
+    value = serialize(df)
+    assert value == serialize_dataframe(df)
+    assert isinstance(value, dict)
+    assert list(value.keys()) == ["columns", "data"]

+ 34 - 0
tests/components/graphing/test_plotly.py

@@ -0,0 +1,34 @@
+import numpy as np
+import plotly.graph_objects as go
+import pytest
+
+from reflex.components.graphing.plotly import serialize_figure  # type: ignore
+from reflex.utils.serializers import serialize
+
+
+@pytest.fixture
+def plotly_fig() -> go.Figure:
+    """Get a plotly figure.
+
+    Returns:
+        A random plotly figure.
+    """
+    # Generate random data.
+    data = np.random.randint(0, 10, size=(10, 4))
+    trace = go.Scatter(
+        x=list(range(len(data))), y=data[:, 0], mode="lines", name="Trace 1"
+    )
+
+    # Create a graph.
+    return go.Figure(data=[trace])
+
+
+def test_serialize_plotly(plotly_fig: go.Figure):
+    """Test that serializing a plotly figure works.
+
+    Args:
+        plotly_fig: The figure to serialize.
+    """
+    value = serialize(plotly_fig)
+    assert isinstance(value, list)
+    assert value == serialize_figure(plotly_fig)

+ 65 - 0
tests/components/media/test_image.py

@@ -0,0 +1,65 @@
+import pytest
+
+try:
+    # PIL is only available in python 3.8+
+    import numpy as np
+    import PIL
+    from PIL.Image import Image as Img
+
+    import reflex as rx
+    from reflex.components.media.image import Image, serialize_image  # type: ignore
+    from reflex.utils.serializers import serialize
+
+    @pytest.fixture
+    def pil_image() -> Img:
+        """Get an image.
+
+        Returns:
+            A random PIL image.
+        """
+        imarray = np.random.rand(100, 100, 3) * 255
+        return PIL.Image.fromarray(imarray.astype("uint8")).convert("RGBA")  # type: ignore
+
+    def test_serialize_image(pil_image: Img):
+        """Test that serializing an image works.
+
+        Args:
+            pil_image: The image to serialize.
+        """
+        data = serialize(pil_image)
+        assert isinstance(data, str)
+        assert data == serialize_image(pil_image)
+        assert data.startswith("data:image/png;base64,")
+
+    def test_set_src_str():
+        """Test that setting the src works."""
+        image = rx.image(src="pic2.jpeg")
+        assert str(image.src) == "pic2.jpeg"  # type: ignore
+
+    def test_set_src_img(pil_image: Img):
+        """Test that setting the src works.
+
+        Args:
+            pil_image: The image to serialize.
+        """
+        image = Image.create(src=pil_image)
+        assert str(image.src) == serialize_image(pil_image)  # type: ignore
+
+    def test_render(pil_image: Img):
+        """Test that rendering an image works.
+
+        Args:
+            pil_image: The image to serialize.
+        """
+        image = Image.create(src=pil_image)
+        assert not image.src.is_string  # type: ignore
+        image._render()
+        assert image.src.is_string  # type: ignore
+
+except ImportError:
+
+    def test_pillow_import():
+        """Make sure the Python version is less than 3.8."""
+        import sys
+
+        assert sys.version_info < (3, 8)

+ 0 - 0
tests/test_prerequites.py → tests/test_prerequisites.py


+ 1 - 6
tests/test_var.py

@@ -230,14 +230,9 @@ def test_create_type_error():
 
     value = ErrorType()
 
-    with pytest.raises(TypeError) as exception:
+    with pytest.raises(TypeError):
         Var.create(value)
 
-    assert (
-        exception.value.args[0]
-        == f"To create a Var must be Var or JSON-serializable. Got {value} of type {type(value)}."
-    )
-
 
 def v(value) -> Var:
     val = (

+ 0 - 0
tests/utils/__init__.py


+ 102 - 0
tests/utils/test_serializers.py

@@ -0,0 +1,102 @@
+import datetime
+from typing import Any, Dict, Type
+
+import pytest
+
+from reflex.utils import serializers
+
+
+@pytest.mark.parametrize(
+    "type_,expected",
+    [
+        (str, True),
+        (dict, True),
+        (Dict[int, int], True),
+    ],
+)
+def test_has_serializer(type_: Type, expected: bool):
+    """Test that has_serializer returns the correct value.
+
+
+    Args:
+        type_: The type to check.
+        expected: The expected result.
+    """
+    assert serializers.has_serializer(type_) == expected
+
+
+@pytest.mark.parametrize(
+    "type_,expected",
+    [
+        (str, serializers.serialize_str),
+        (dict, serializers.serialize_dict),
+        (Dict[int, int], serializers.serialize_dict),
+        (datetime.datetime, serializers.serialize_datetime),
+        (datetime.date, serializers.serialize_datetime),
+        (datetime.time, serializers.serialize_datetime),
+        (datetime.timedelta, serializers.serialize_datetime),
+    ],
+)
+def test_get_serializer(type_: Type, expected: serializers.Serializer):
+    """Test that get_serializer returns the correct value.
+
+
+    Args:
+        type_: The type to check.
+        expected: The expected result.
+    """
+    assert serializers.get_serializer(type_) == expected
+
+
+def test_add_serializer():
+    """Test that adding a serializer works."""
+
+    def serialize_test(value: int) -> str:
+        """Serialize an int to a string.
+
+        Args:
+            value: The value to serialize.
+
+        Returns:
+            The serialized value.
+        """
+        return str(value)
+
+    # Initially there should be no serializer for int.
+    assert not serializers.has_serializer(int)
+    assert serializers.serialize(5) is None
+
+    # Register the serializer.
+    assert serializers.serializer(serialize_test) == serialize_test
+
+    # There should now be a serializer for int.
+    assert serializers.has_serializer(int)
+    assert serializers.get_serializer(int) == serialize_test
+    assert serializers.serialize(5) == "5"
+
+    # Remove the serializer.
+    serializers.SERIALIZERS.pop(int)
+
+
+@pytest.mark.parametrize(
+    "value,expected",
+    [
+        ("test", "test"),
+        (datetime.datetime(2021, 1, 1, 1, 1, 1, 1), "2021-01-01 01:01:01.000001"),
+        (datetime.date(2021, 1, 1), "2021-01-01"),
+        (datetime.time(1, 1, 1, 1), "01:01:01.000001"),
+        (datetime.timedelta(1, 1, 1), "1 day, 0:00:01.000001"),
+        (5, None),
+        (None, None),
+        ([], None),
+    ],
+)
+def test_serialize(value: Any, expected: str):
+    """Test that serialize returns the correct value.
+
+
+    Args:
+        value: The value to serialize.
+        expected: The expected result.
+    """
+    assert serializers.serialize(value) == expected

+ 2 - 1
tests/test_utils.py → tests/utils/test_utils.py

@@ -21,6 +21,7 @@ from reflex.utils import (
     types,
 )
 from reflex.utils import exec as utils_exec
+from reflex.utils.serializers import serialize
 from reflex.vars import BaseVar, Var
 
 
@@ -777,4 +778,4 @@ def test_style_prop_with_event_handler_value(callable):
     }
 
     with pytest.raises(TypeError):
-        format.format_dict(style)  # type: ignore
+        serialize(style)  # type: ignore

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff