1
0
Эх сурвалжийг харах

Add serializers for different var types (#1816)

Nikhil Rao 1 жил өмнө
parent
commit
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]]
 [[package]]
 name = "alembic"
 name = "alembic"
-version = "1.11.1"
+version = "1.12.0"
 description = "A database migration tool for SQLAlchemy."
 description = "A database migration tool for SQLAlchemy."
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 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]
 [package.dependencies]
@@ -45,13 +45,13 @@ trio = ["trio (<0.22)"]
 
 
 [[package]]
 [[package]]
 name = "async-timeout"
 name = "async-timeout"
-version = "4.0.2"
+version = "4.0.3"
 description = "Timeout context manager for asyncio programs"
 description = "Timeout context manager for asyncio programs"
 optional = false
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 files = [
 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]
 [package.dependencies]
@@ -230,24 +230,24 @@ pycparser = "*"
 
 
 [[package]]
 [[package]]
 name = "cfgv"
 name = "cfgv"
-version = "3.3.1"
+version = "3.4.0"
 description = "Validate configuration and produce human readable error messages."
 description = "Validate configuration and produce human readable error messages."
 optional = false
 optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.8"
 files = [
 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]]
 [[package]]
 name = "click"
 name = "click"
-version = "8.1.6"
+version = "8.1.7"
 description = "Composable command line interface toolkit"
 description = "Composable command line interface toolkit"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 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]
 [package.dependencies]
@@ -386,13 +386,13 @@ files = [
 
 
 [[package]]
 [[package]]
 name = "exceptiongroup"
 name = "exceptiongroup"
-version = "1.1.2"
+version = "1.1.3"
 description = "Backport of PEP 654 (exception groups)"
 description = "Backport of PEP 654 (exception groups)"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 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]
 [package.extras]
@@ -421,18 +421,19 @@ test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6
 
 
 [[package]]
 [[package]]
 name = "filelock"
 name = "filelock"
-version = "3.12.2"
+version = "3.12.4"
 description = "A platform independent file lock."
 description = "A platform independent file lock."
 optional = false
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
 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]
 [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]]
 [[package]]
 name = "greenlet"
 name = "greenlet"
@@ -587,13 +588,13 @@ socks = ["socksio (==1.*)"]
 
 
 [[package]]
 [[package]]
 name = "identify"
 name = "identify"
-version = "2.5.26"
+version = "2.5.29"
 description = "File identification library for Python"
 description = "File identification library for Python"
 optional = false
 optional = false
 python-versions = ">=3.8"
 python-versions = ">=3.8"
 files = [
 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]
 [package.extras]
@@ -1032,8 +1033,8 @@ files = [
 [package.dependencies]
 [package.dependencies]
 numpy = [
 numpy = [
     {version = ">=1.20.3", markers = "python_version < \"3.10\""},
     {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.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"
 python-dateutil = ">=2.8.1"
 pytz = ">=2020.1"
 pytz = ">=2020.1"
@@ -1052,6 +1053,73 @@ files = [
     {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
     {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]]
 [[package]]
 name = "platformdirs"
 name = "platformdirs"
 version = "3.10.0"
 version = "3.10.0"
@@ -1072,13 +1140,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
 
 
 [[package]]
 [[package]]
 name = "plotly"
 name = "plotly"
-version = "5.15.0"
+version = "5.17.0"
 description = "An open-source, interactive data visualization library for Python"
 description = "An open-source, interactive data visualization library for Python"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 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]
 [package.dependencies]
@@ -1105,13 +1173,13 @@ testing = ["pytest", "pytest-benchmark"]
 
 
 [[package]]
 [[package]]
 name = "pre-commit"
 name = "pre-commit"
-version = "3.3.3"
+version = "3.4.0"
 description = "A framework for managing and maintaining multi-language pre-commit hooks."
 description = "A framework for managing and maintaining multi-language pre-commit hooks."
 optional = false
 optional = false
 python-versions = ">=3.8"
 python-versions = ">=3.8"
 files = [
 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]
 [package.dependencies]
@@ -1212,13 +1280,13 @@ email = ["email-validator (>=1.0.3)"]
 
 
 [[package]]
 [[package]]
 name = "pygments"
 name = "pygments"
-version = "2.15.1"
+version = "2.16.1"
 description = "Pygments is a syntax highlighting package written in Python."
 description = "Pygments is a syntax highlighting package written in Python."
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 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]
 [package.extras]
@@ -1226,13 +1294,13 @@ plugins = ["importlib-metadata"]
 
 
 [[package]]
 [[package]]
 name = "pyright"
 name = "pyright"
-version = "1.1.318"
+version = "1.1.327"
 description = "Command line wrapper for pyright"
 description = "Command line wrapper for pyright"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 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]
 [package.dependencies]
@@ -1257,13 +1325,13 @@ files = [
 
 
 [[package]]
 [[package]]
 name = "pytest"
 name = "pytest"
-version = "7.4.0"
+version = "7.4.2"
 description = "pytest: simple powerful testing with Python"
 description = "pytest: simple powerful testing with Python"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 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]
 [package.dependencies]
@@ -1348,15 +1416,18 @@ six = ">=1.5"
 
 
 [[package]]
 [[package]]
 name = "python-engineio"
 name = "python-engineio"
-version = "4.5.1"
+version = "4.7.1"
 description = "Engine.IO server and client for Python"
 description = "Engine.IO server and client for Python"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 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]
 [package.extras]
 asyncio-client = ["aiohttp (>=3.4)"]
 asyncio-client = ["aiohttp (>=3.4)"]
 client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
 client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
@@ -1377,32 +1448,33 @@ six = ">=1.4.0"
 
 
 [[package]]
 [[package]]
 name = "python-socketio"
 name = "python-socketio"
-version = "5.8.0"
+version = "5.9.0"
 description = "Socket.IO server and client for Python"
 description = "Socket.IO server and client for Python"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 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]
 [package.dependencies]
 bidict = ">=0.21.0"
 bidict = ">=0.21.0"
-python-engineio = ">=4.3.0"
+python-engineio = ">=4.7.0"
 
 
 [package.extras]
 [package.extras]
 asyncio-client = ["aiohttp (>=3.4)"]
 asyncio-client = ["aiohttp (>=3.4)"]
 client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
 client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
+docs = ["sphinx"]
 
 
 [[package]]
 [[package]]
 name = "pytz"
 name = "pytz"
-version = "2023.3"
+version = "2023.3.post1"
 description = "World timezone definitions, modern and historical"
 description = "World timezone definitions, modern and historical"
 optional = false
 optional = false
 python-versions = "*"
 python-versions = "*"
 files = [
 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]]
 [[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_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_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-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-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
     {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
     {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"},
     {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_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_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-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-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
     {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
     {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-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_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
     {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
     {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_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_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-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-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
     {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
     {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"},
     {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_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_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-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-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
     {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
     {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
     {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
     {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]]
 [[package]]
 name = "rich"
 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"
 description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
 optional = false
 optional = false
 python-versions = ">=3.7.0"
 python-versions = ">=3.7.0"
 files = [
 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]
 [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 = ["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"]
 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]]
 [[package]]
 name = "six"
 name = "six"
 version = "1.16.0"
 version = "1.16.0"
@@ -1635,7 +1731,7 @@ files = [
 ]
 ]
 
 
 [package.dependencies]
 [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\""}
 importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
 
 
 [package.extras]
 [package.extras]
@@ -1731,13 +1827,13 @@ test = ["aiomysql (>=0.1.1,<0.2.0)", "aiosqlite (>=0.17.0,<0.20.0)", "arrow (>=1
 
 
 [[package]]
 [[package]]
 name = "tenacity"
 name = "tenacity"
-version = "8.2.2"
+version = "8.2.3"
 description = "Retry code until it succeeds"
 description = "Retry code until it succeeds"
 optional = false
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 files = [
 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]
 [package.extras]
@@ -1787,13 +1883,13 @@ sortedcontainers = "*"
 
 
 [[package]]
 [[package]]
 name = "trio-websocket"
 name = "trio-websocket"
-version = "0.10.3"
+version = "0.10.4"
 description = "WebSocket library for Trio"
 description = "WebSocket library for Trio"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 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]
 [package.dependencies]
@@ -1923,13 +2019,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
 
 
 [[package]]
 [[package]]
 name = "virtualenv"
 name = "virtualenv"
-version = "20.24.2"
+version = "20.24.5"
 description = "Virtual Python Environment builder"
 description = "Virtual Python Environment builder"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 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]
 [package.dependencies]
@@ -1938,7 +2034,7 @@ filelock = ">=3.12.2,<4"
 platformdirs = ">=3.9.1,<4"
 platformdirs = ">=3.9.1,<4"
 
 
 [package.extras]
 [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)"]
 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]]
 [[package]]
@@ -2125,4 +2221,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
 [metadata]
 [metadata]
 lock-version = "2.0"
 lock-version = "2.0"
 python-versions = "^3.7"
 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"
 gunicorn = "^20.1.0"
 httpx = "^0.24.0"
 httpx = "^0.24.0"
 jinja2 = "^3.1.2"
 jinja2 = "^3.1.2"
-plotly = "^5.13.0"
 psutil = "^5.9.4"
 psutil = "^5.9.4"
 pydantic = "^1.10.2"
 pydantic = "^1.10.2"
 python-multipart = "^0.0.5"
 python-multipart = "^0.0.5"
@@ -63,6 +62,10 @@ pandas = [
     {version = "^1.5.3", python = ">=3.8,<4.0"},
     {version = "^1.5.3", python = ">=3.8,<4.0"},
     {version = "^1.1", python = ">=3.7, <3.8"}
     {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"
 asynctest = "^0.13.0"
 pre-commit = {version = "^3.2.1", python = ">=3.8,<4.0"}
 pre-commit = {version = "^3.2.1", python = ">=3.8,<4.0"}
 selenium = "^4.11.0"
 selenium = "^4.11.0"

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

@@ -1,10 +1,13 @@
 """Table components."""
 """Table components."""
 
 
+from __future__ import annotations
+
 from typing import Any, Dict, List, Union
 from typing import Any, Dict, List, Union
 
 
 from reflex.components.component import Component
 from reflex.components.component import Component
 from reflex.components.tags import Tag
 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
 from reflex.vars import BaseVar, ComputedVar, ImportVar, Var
 
 
 
 
@@ -106,23 +109,59 @@ class DataTable(Gridjs):
         )
         )
 
 
     def _render(self) -> Tag:
     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 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.
         # Render the table.
         return super()._render()
         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.
             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.
             orientation: The orientation of the tab list.
             variant: "line" | "enclosed" | "enclosed-colored" | "soft-rounded" | "solid-rounded" | "unstyled"
             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)
             items: The items for the tabs component, a list of tuple (label, panel)
             **props: The properties of the component.
             **props: The properties of the component.
 
 

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
reflex/components/forms/select.pyi


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

@@ -1,13 +1,17 @@
 """Component for displaying a plotly graph."""
 """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.component import NoSSRComponent
-from reflex.components.tags import Tag
+from reflex.utils.serializers import serializer
 from reflex.vars import Var
 from reflex.vars import Var
 
 
+try:
+    from plotly.graph_objects import Figure
+except ImportError:
+    Figure = Any
+
 
 
 class PlotlyLib(NoSSRComponent):
 class PlotlyLib(NoSSRComponent):
     """A component that wraps a plotly lib."""
     """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.
     # If true, the graph will resize when the window is resized.
     use_resize_handler: Var[bool]
     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`!
 # 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 Component
 from reflex.components.component import NoSSRComponent
 from reflex.components.component import NoSSRComponent
 from reflex.vars import Var, BaseVar, ComputedVar
 from reflex.vars import Var, BaseVar, ComputedVar
@@ -31,7 +31,7 @@ class PlotlyLib(NoSSRComponent):
 class Plotly(PlotlyLib):
 class Plotly(PlotlyLib):
     @overload
     @overload
     @classmethod
     @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.
         """Create the component.
 
 
         Args:
         Args:

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

@@ -1,12 +1,14 @@
 """An image component."""
 """An image component."""
 from __future__ import annotations
 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.component import Component
 from reflex.components.libs.chakra import ChakraComponent
 from reflex.components.libs.chakra import ChakraComponent
 from reflex.components.tags import Tag
 from reflex.components.tags import Tag
-from reflex.utils import format, types
+from reflex.utils.serializers import serializer
 from reflex.vars import Var
 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)_
     # Learn more _[here](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)_
     src_set: Var[str]
     src_set: Var[str]
 
 
-    def get_triggers(self) -> Set[str]:
+    def get_triggers(self) -> set[str]:
         """Get the event triggers for the component.
         """Get the event triggers for the component.
 
 
         Returns:
         Returns:
@@ -60,9 +62,30 @@ class Image(ChakraComponent):
         return super().get_triggers() | {"on_error", "on_load"}
         return super().get_triggers() | {"on_error", "on_load"}
 
 
     def _render(self) -> Tag:
     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.
         # Render the table.
         return super()._render()
         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`!
 # 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.libs.chakra import ChakraComponent
 from reflex.components.component import Component
 from reflex.components.component import Component
 from reflex.vars import Var, BaseVar, ComputedVar
 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,
                 target_metadata=sqlmodel.SQLModel.metadata,
                 render_item=cls._alembic_render_item,
                 render_item=cls._alembic_render_item,
                 process_revision_directives=writer,  # type: ignore
                 process_revision_directives=writer,  # type: ignore
+                compare_type=False,
             )
             )
             env.run_migrations()
             env.run_migrations()
         changes_detected = False
         changes_detected = False

+ 10 - 127
reflex/utils/format.py

@@ -2,22 +2,16 @@
 
 
 from __future__ import annotations
 from __future__ import annotations
 
 
-import base64
-import io
 import json
 import json
 import os
 import os
 import os.path as op
 import os.path as op
 import re
 import re
 import sys
 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 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
 from reflex.vars import Var
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
@@ -316,12 +310,9 @@ def format_prop(
                 return prop
                 return prop
             return json_dumps(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.
         # For dictionaries, convert any properties to strings.
         elif isinstance(prop, dict):
         elif isinstance(prop, dict):
-            prop = format_dict(prop)
+            prop = serializers.serialize_dict(prop)  # type: ignore
 
 
         else:
         else:
             # Dump the prop as JSON.
             # 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()}
     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:
 def format_state(value: Any) -> Any:
     """Recursively format values in the given state.
     """Recursively format values in the given state.
 
 
@@ -523,30 +476,12 @@ def format_state(value: Any) -> Any:
     if isinstance(value, types.StateBases):
     if isinstance(value, types.StateBases):
         return value
         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:
 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}"
     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]]:
 def format_breadcrumbs(route: str) -> list[tuple[str, str]]:
     """Take a route and return a list of tuple for use in breadcrumb.
     """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 contextlib
 import typing
 import typing
-from datetime import date, datetime, time, timedelta
 from typing import Any, Callable, Type, Union, _GenericAlias  # type: ignore
 from typing import Any, Callable, Type, Union, _GenericAlias  # type: ignore
 
 
 from reflex.base import Base
 from reflex.base import Base
+from reflex.utils import serializers
 
 
 # Union of generic types.
 # Union of generic types.
 GenericType = Union[Type, _GenericAlias]
 GenericType = Union[Type, _GenericAlias]
@@ -143,60 +143,16 @@ def is_dataframe(value: Type) -> bool:
     return value.__name__ == "DataFrame"
     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:
     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:
     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:
 def is_backend_variable(name: str) -> bool:

+ 7 - 11
reflex/vars.py

@@ -24,13 +24,12 @@ from typing import (
     get_type_hints,
     get_type_hints,
 )
 )
 
 
-from plotly.graph_objects import Figure
-from plotly.io import to_json
 from pydantic.fields import ModelField
 from pydantic.fields import ModelField
 
 
 from reflex import constants
 from reflex import constants
 from reflex.base import Base
 from reflex.base import Base
 from reflex.utils import console, format, types
 from reflex.utils import console, format, types
+from reflex.utils.serializers import serialize
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from reflex.state import State
     from reflex.state import State
@@ -126,19 +125,16 @@ class Var(ABC):
 
 
         type_ = type(value)
         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:
         try:
             name = value if isinstance(value, str) else json.dumps(value)
             name = value if isinstance(value, str) else json.dumps(value)
         except TypeError as e:
         except TypeError as e:
             raise TypeError(
             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
             ) from e
 
 
         return BaseVar(name=name, type_=type_, is_local=is_local, is_string=is_string)
         return BaseVar(name=name, type_=type_, is_local=is_local, is_string=is_string)
@@ -184,7 +180,7 @@ class Var(ABC):
         """
         """
         if self.state:
         if self.state:
             return self.full_name
             return self.full_name
-        if self.is_string or self.type_ is Figure:
+        if self.is_string:
             return self.name
             return self.name
         try:
         try:
             return json.loads(self.name)
             return json.loads(self.name)

+ 9 - 2
scripts/pyi_generator.py

@@ -7,7 +7,7 @@ import re
 import sys
 import sys
 from inspect import getfullargspec
 from inspect import getfullargspec
 from pathlib import Path
 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
 import black
 
 
@@ -181,7 +181,14 @@ class PyiGenerator:
         return _get_var_definition(self.current_module, _name)
         return _get_var_definition(self.current_module, _name)
 
 
     def _generate_function(self, _name, _func):
     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}:", "    ..."]
         return [f"{definition}:", "    ..."]
 
 
     def _write_pyi_file(self, variables, functions, classes):
     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 pytest
 
 
 import reflex as rx
 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 import types
+from reflex.utils.serializers import serialize
 
 
 
 
 @pytest.mark.parametrize(
 @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_):
     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
             data=data_table_state.data, columns=data_table_state.columns
         )
         )
     else:
     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()
     data_table_dict = data_table_component.render()
 
 
@@ -62,7 +66,7 @@ def test_invalid_props(props):
         props: props to pass in component.
         props: props to pass in component.
     """
     """
     with pytest.raises(ValueError):
     with pytest.raises(ValueError):
-        data_table(**props)
+        DataTable.create(**props)
 
 
 
 
 @pytest.mark.parametrize(
 @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:
     with pytest.raises(ValueError) as err:
         if is_data_frame:
         if is_data_frame:
-            data_table(data=request.getfixturevalue(fixture).data)
+            DataTable.create(data=request.getfixturevalue(fixture).data)
         else:
         else:
-            data_table(
+            DataTable.create(
                 data=request.getfixturevalue(fixture).data,
                 data=request.getfixturevalue(fixture).data,
                 columns=request.getfixturevalue(fixture).columns,
                 columns=request.getfixturevalue(fixture).columns,
             )
             )
     assert err.value.args[0] == err_msg
     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()
     value = ErrorType()
 
 
-    with pytest.raises(TypeError) as exception:
+    with pytest.raises(TypeError):
         Var.create(value)
         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:
 def v(value) -> Var:
     val = (
     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,
     types,
 )
 )
 from reflex.utils import exec as utils_exec
 from reflex.utils import exec as utils_exec
+from reflex.utils.serializers import serialize
 from reflex.vars import BaseVar, Var
 from reflex.vars import BaseVar, Var
 
 
 
 
@@ -777,4 +778,4 @@ def test_style_prop_with_event_handler_value(callable):
     }
     }
 
 
     with pytest.raises(TypeError):
     with pytest.raises(TypeError):
-        format.format_dict(style)  # type: ignore
+        serialize(style)  # type: ignore

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно