Browse Source

Clean up on_load events (#965)

Nikhil Rao 2 năm trước cách đây
mục cha
commit
8e3daf9f5b

+ 119 - 138
poetry.lock

@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand.
 
 
 [[package]]
 [[package]]
 name = "anyio"
 name = "anyio"
@@ -37,25 +37,6 @@ files = [
 [package.dependencies]
 [package.dependencies]
 typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
 typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
 
 
-[[package]]
-name = "attrs"
-version = "22.2.0"
-description = "Classes Without Boilerplate"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
-    {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"},
-    {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"},
-]
-
-[package.extras]
-cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
-dev = ["attrs[docs,tests]"]
-docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"]
-tests = ["attrs[tests-no-zope]", "zope.interface"]
-tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
-
 [[package]]
 [[package]]
 name = "bidict"
 name = "bidict"
 version = "0.22.1"
 version = "0.22.1"
@@ -112,14 +93,14 @@ uvloop = ["uvloop (>=0.15.2)"]
 
 
 [[package]]
 [[package]]
 name = "certifi"
 name = "certifi"
-version = "2022.12.7"
+version = "2023.5.7"
 description = "Python package for providing Mozilla's CA Bundle."
 description = "Python package for providing Mozilla's CA Bundle."
 category = "main"
 category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
-    {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
-    {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
+    {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"},
+    {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"},
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -215,14 +196,14 @@ files = [
 
 
 [[package]]
 [[package]]
 name = "exceptiongroup"
 name = "exceptiongroup"
-version = "1.1.0"
+version = "1.1.1"
 description = "Backport of PEP 654 (exception groups)"
 description = "Backport of PEP 654 (exception groups)"
 category = "dev"
 category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"},
-    {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"},
+    {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"},
+    {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"},
 ]
 ]
 
 
 [package.extras]
 [package.extras]
@@ -252,19 +233,19 @@ test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.10.0)", "coverage[toml] (>=
 
 
 [[package]]
 [[package]]
 name = "filelock"
 name = "filelock"
-version = "3.10.6"
+version = "3.12.0"
 description = "A platform independent file lock."
 description = "A platform independent file lock."
 category = "dev"
 category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "filelock-3.10.6-py3-none-any.whl", hash = "sha256:52f119747b2b9c4730dac715a7b1ab34b8ee70fd9259cba158ee53da566387ff"},
-    {file = "filelock-3.10.6.tar.gz", hash = "sha256:409105becd604d6b176a483f855e7e8903c5cb2873e47f2c64f66a370c046aaf"},
+    {file = "filelock-3.12.0-py3-none-any.whl", hash = "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9"},
+    {file = "filelock-3.12.0.tar.gz", hash = "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718"},
 ]
 ]
 
 
 [package.extras]
 [package.extras]
-docs = ["furo (>=2022.12.7)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.2.2)", "diff-cover (>=7.5)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"]
+docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"]
 
 
 [[package]]
 [[package]]
 name = "greenlet"
 name = "greenlet"
@@ -424,14 +405,14 @@ socks = ["socksio (>=1.0.0,<2.0.0)"]
 
 
 [[package]]
 [[package]]
 name = "identify"
 name = "identify"
-version = "2.5.22"
+version = "2.5.24"
 description = "File identification library for Python"
 description = "File identification library for Python"
 category = "dev"
 category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "identify-2.5.22-py2.py3-none-any.whl", hash = "sha256:f0faad595a4687053669c112004178149f6c326db71ee999ae4636685753ad2f"},
-    {file = "identify-2.5.22.tar.gz", hash = "sha256:f7a93d6cf98e29bd07663c60728e7a4057615068d7a639d132dc883b2d54d31e"},
+    {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"},
+    {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"},
 ]
 ]
 
 
 [package.extras]
 [package.extras]
@@ -451,14 +432,14 @@ files = [
 
 
 [[package]]
 [[package]]
 name = "importlib-metadata"
 name = "importlib-metadata"
-version = "6.0.0"
+version = "6.6.0"
 description = "Read metadata from Python packages"
 description = "Read metadata from Python packages"
 category = "main"
 category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"},
-    {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"},
+    {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"},
+    {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"},
 ]
 ]
 
 
 [package.dependencies]
 [package.dependencies]
@@ -552,52 +533,52 @@ files = [
 
 
 [[package]]
 [[package]]
 name = "numpy"
 name = "numpy"
-version = "1.24.2"
+version = "1.24.3"
 description = "Fundamental package for array computing in Python"
 description = "Fundamental package for array computing in Python"
 category = "dev"
 category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.8"
 python-versions = ">=3.8"
 files = [
 files = [
-    {file = "numpy-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef70b4fc1e872ebddc38cddacc87c19a3709c0e3e5d20bf3954c147b1dd941d"},
-    {file = "numpy-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d2859428712785e8a8b7d2b3ef0a1d1565892367b32f915c4a4df44d0e64f5"},
-    {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6524630f71631be2dabe0c541e7675db82651eb998496bbe16bc4f77f0772253"},
-    {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a51725a815a6188c662fb66fb32077709a9ca38053f0274640293a14fdd22978"},
-    {file = "numpy-1.24.2-cp310-cp310-win32.whl", hash = "sha256:2620e8592136e073bd12ee4536149380695fbe9ebeae845b81237f986479ffc9"},
-    {file = "numpy-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:97cf27e51fa078078c649a51d7ade3c92d9e709ba2bfb97493007103c741f1d0"},
-    {file = "numpy-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7de8fdde0003f4294655aa5d5f0a89c26b9f22c0a58790c38fae1ed392d44a5a"},
-    {file = "numpy-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4173bde9fa2a005c2c6e2ea8ac1618e2ed2c1c6ec8a7657237854d42094123a0"},
-    {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cecaed30dc14123020f77b03601559fff3e6cd0c048f8b5289f4eeabb0eb281"},
-    {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a23f8440561a633204a67fb44617ce2a299beecf3295f0d13c495518908e910"},
-    {file = "numpy-1.24.2-cp311-cp311-win32.whl", hash = "sha256:e428c4fbfa085f947b536706a2fc349245d7baa8334f0c5723c56a10595f9b95"},
-    {file = "numpy-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:557d42778a6869c2162deb40ad82612645e21d79e11c1dc62c6e82a2220ffb04"},
-    {file = "numpy-1.24.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d0a2db9d20117bf523dde15858398e7c0858aadca7c0f088ac0d6edd360e9ad2"},
-    {file = "numpy-1.24.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c72a6b2f4af1adfe193f7beb91ddf708ff867a3f977ef2ec53c0ffb8283ab9f5"},
-    {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29e6bd0ec49a44d7690ecb623a8eac5ab8a923bce0bea6293953992edf3a76a"},
-    {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eabd64ddb96a1239791da78fa5f4e1693ae2dadc82a76bc76a14cbb2b966e96"},
-    {file = "numpy-1.24.2-cp38-cp38-win32.whl", hash = "sha256:e3ab5d32784e843fc0dd3ab6dcafc67ef806e6b6828dc6af2f689be0eb4d781d"},
-    {file = "numpy-1.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756"},
-    {file = "numpy-1.24.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4199e7cfc307a778f72d293372736223e39ec9ac096ff0a2e64853b866a8e18a"},
-    {file = "numpy-1.24.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adbdce121896fd3a17a77ab0b0b5eedf05a9834a18699db6829a64e1dfccca7f"},
-    {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889b2cc88b837d86eda1b17008ebeb679d82875022200c6e8e4ce6cf549b7acb"},
-    {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f64bb98ac59b3ea3bf74b02f13836eb2e24e48e0ab0145bbda646295769bd780"},
-    {file = "numpy-1.24.2-cp39-cp39-win32.whl", hash = "sha256:63e45511ee4d9d976637d11e6c9864eae50e12dc9598f531c035265991910468"},
-    {file = "numpy-1.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:a77d3e1163a7770164404607b7ba3967fb49b24782a6ef85d9b5f54126cc39e5"},
-    {file = "numpy-1.24.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:92011118955724465fb6853def593cf397b4a1367495e0b59a7e69d40c4eb71d"},
-    {file = "numpy-1.24.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9006288bcf4895917d02583cf3411f98631275bc67cce355a7f39f8c14338fa"},
-    {file = "numpy-1.24.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:150947adbdfeceec4e5926d956a06865c1c690f2fd902efede4ca6fe2e657c3f"},
-    {file = "numpy-1.24.2.tar.gz", hash = "sha256:003a9f530e880cb2cd177cba1af7220b9aa42def9c4afc2a2fc3ee6be7eb2b22"},
+    {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"},
+    {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"},
+    {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"},
+    {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"},
+    {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"},
+    {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"},
+    {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"},
+    {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"},
+    {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"},
+    {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"},
+    {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"},
+    {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"},
+    {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"},
+    {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"},
+    {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"},
+    {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"},
+    {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"},
+    {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"},
+    {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"},
+    {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"},
+    {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"},
+    {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"},
+    {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"},
+    {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"},
+    {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"},
+    {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"},
+    {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"},
+    {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"},
 ]
 ]
 
 
 [[package]]
 [[package]]
 name = "packaging"
 name = "packaging"
-version = "23.0"
+version = "23.1"
 description = "Core utilities for Python packages"
 description = "Core utilities for Python packages"
-category = "dev"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"},
-    {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
+    {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
+    {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -693,48 +674,49 @@ test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"]
 
 
 [[package]]
 [[package]]
 name = "pathspec"
 name = "pathspec"
-version = "0.11.0"
+version = "0.11.1"
 description = "Utility library for gitignore style pattern matching of file paths."
 description = "Utility library for gitignore style pattern matching of file paths."
 category = "dev"
 category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"},
-    {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"},
+    {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
+    {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
 ]
 ]
 
 
 [[package]]
 [[package]]
 name = "platformdirs"
 name = "platformdirs"
-version = "3.1.0"
+version = "3.5.0"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 category = "dev"
 category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "platformdirs-3.1.0-py3-none-any.whl", hash = "sha256:13b08a53ed71021350c9e300d4ea8668438fb0046ab3937ac9a29913a1a1350a"},
-    {file = "platformdirs-3.1.0.tar.gz", hash = "sha256:accc3665857288317f32c7bebb5a8e482ba717b474f3fc1d18ca7f9214be0cef"},
+    {file = "platformdirs-3.5.0-py3-none-any.whl", hash = "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4"},
+    {file = "platformdirs-3.5.0.tar.gz", hash = "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"},
 ]
 ]
 
 
 [package.dependencies]
 [package.dependencies]
-typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""}
+typing-extensions = {version = ">=4.5", markers = "python_version < \"3.8\""}
 
 
 [package.extras]
 [package.extras]
-docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
+docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
 
 
 [[package]]
 [[package]]
 name = "plotly"
 name = "plotly"
-version = "5.13.1"
+version = "5.14.1"
 description = "An open-source, interactive data visualization library for Python"
 description = "An open-source, interactive data visualization library for Python"
 category = "main"
 category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
-    {file = "plotly-5.13.1-py2.py3-none-any.whl", hash = "sha256:f776a5c664908450c6c1727f61e8e2e22798d9c6c69d37a9057735365084a2fa"},
-    {file = "plotly-5.13.1.tar.gz", hash = "sha256:90ee9a1fee0dda30e2830e129855081ea17bd1b06a553a62b62de15caff1a219"},
+    {file = "plotly-5.14.1-py2.py3-none-any.whl", hash = "sha256:a63f3ad9e4cc2e02902a738e5e3e7f3d1307f2732ac71a6c28f1238ed3052826"},
+    {file = "plotly-5.14.1.tar.gz", hash = "sha256:bcac86d7fcba3eff7260c1eddc36ca34dae2aded10a0709808446565e0e53b93"},
 ]
 ]
 
 
 [package.dependencies]
 [package.dependencies]
+packaging = "*"
 tenacity = ">=6.2.0"
 tenacity = ">=6.2.0"
 
 
 [[package]]
 [[package]]
@@ -758,14 +740,14 @@ testing = ["pytest", "pytest-benchmark"]
 
 
 [[package]]
 [[package]]
 name = "pre-commit"
 name = "pre-commit"
-version = "3.2.1"
+version = "3.3.1"
 description = "A framework for managing and maintaining multi-language pre-commit hooks."
 description = "A framework for managing and maintaining multi-language pre-commit hooks."
 category = "dev"
 category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.8"
 python-versions = ">=3.8"
 files = [
 files = [
-    {file = "pre_commit-3.2.1-py2.py3-none-any.whl", hash = "sha256:a06a7fcce7f420047a71213c175714216498b49ebc81fe106f7716ca265f5bb6"},
-    {file = "pre_commit-3.2.1.tar.gz", hash = "sha256:b5aee7d75dbba21ee161ba641b01e7ae10c5b91967ebf7b2ab0dfae12d07e1f1"},
+    {file = "pre_commit-3.3.1-py2.py3-none-any.whl", hash = "sha256:218e9e3f7f7f3271ebc355a15598a4d3893ad9fc7b57fe446db75644543323b9"},
+    {file = "pre_commit-3.3.1.tar.gz", hash = "sha256:733f78c9a056cdd169baa6cd4272d51ecfda95346ef8a89bf93712706021b907"},
 ]
 ]
 
 
 [package.dependencies]
 [package.dependencies]
@@ -777,26 +759,26 @@ virtualenv = ">=20.10.0"
 
 
 [[package]]
 [[package]]
 name = "psutil"
 name = "psutil"
-version = "5.9.4"
+version = "5.9.5"
 description = "Cross-platform lib for process and system monitoring in Python."
 description = "Cross-platform lib for process and system monitoring in Python."
 category = "main"
 category = "main"
 optional = false
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
 files = [
-    {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"},
-    {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"},
-    {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"},
-    {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"},
-    {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"},
-    {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"},
-    {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"},
-    {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"},
-    {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"},
-    {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"},
-    {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"},
-    {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"},
-    {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"},
-    {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"},
+    {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"},
+    {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"},
+    {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"},
+    {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"},
+    {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"},
+    {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"},
+    {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"},
+    {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"},
+    {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"},
+    {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"},
+    {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"},
+    {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"},
+    {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"},
+    {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"},
 ]
 ]
 
 
 [package.extras]
 [package.extras]
@@ -857,14 +839,14 @@ email = ["email-validator (>=1.0.3)"]
 
 
 [[package]]
 [[package]]
 name = "pygments"
 name = "pygments"
-version = "2.14.0"
+version = "2.15.1"
 description = "Pygments is a syntax highlighting package written in Python."
 description = "Pygments is a syntax highlighting package written in Python."
 category = "main"
 category = "main"
 optional = false
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 files = [
 files = [
-    {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"},
-    {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"},
+    {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"},
+    {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"},
 ]
 ]
 
 
 [package.extras]
 [package.extras]
@@ -872,14 +854,14 @@ plugins = ["importlib-metadata"]
 
 
 [[package]]
 [[package]]
 name = "pyright"
 name = "pyright"
-version = "1.1.297"
+version = "1.1.306"
 description = "Command line wrapper for pyright"
 description = "Command line wrapper for pyright"
 category = "dev"
 category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "pyright-1.1.297-py3-none-any.whl", hash = "sha256:3fd6528280eb649f8b64b7ece55299f01e340d29f4cf257da876957e3ee24062"},
-    {file = "pyright-1.1.297.tar.gz", hash = "sha256:89082de2fbd240fa75767b57824f4d8516f2fb9005047265a67b895547c6272f"},
+    {file = "pyright-1.1.306-py3-none-any.whl", hash = "sha256:008eb2a29584ae274a154d749cf81476a3073fb562a462eac8d43a753378b9db"},
+    {file = "pyright-1.1.306.tar.gz", hash = "sha256:16d5d198be64de497d5f9002000a271176c381e21b977ca5566cf779b643c9ed"},
 ]
 ]
 
 
 [package.dependencies]
 [package.dependencies]
@@ -892,18 +874,17 @@ dev = ["twine (>=3.4.1)"]
 
 
 [[package]]
 [[package]]
 name = "pytest"
 name = "pytest"
-version = "7.2.2"
+version = "7.3.1"
 description = "pytest: simple powerful testing with Python"
 description = "pytest: simple powerful testing with Python"
 category = "dev"
 category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"},
-    {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"},
+    {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"},
+    {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"},
 ]
 ]
 
 
 [package.dependencies]
 [package.dependencies]
-attrs = ">=19.2.0"
 colorama = {version = "*", markers = "sys_platform == \"win32\""}
 colorama = {version = "*", markers = "sys_platform == \"win32\""}
 exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
 exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
 importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
 importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
@@ -913,7 +894,7 @@ pluggy = ">=0.12,<2.0"
 tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
 tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
 
 
 [package.extras]
 [package.extras]
-testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
+testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
 
 
 [[package]]
 [[package]]
 name = "pytest-asyncio"
 name = "pytest-asyncio"
@@ -970,14 +951,14 @@ six = ">=1.5"
 
 
 [[package]]
 [[package]]
 name = "python-engineio"
 name = "python-engineio"
-version = "4.3.4"
+version = "4.4.1"
 description = "Engine.IO server and client for Python"
 description = "Engine.IO server and client for Python"
 category = "main"
 category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
-    {file = "python-engineio-4.3.4.tar.gz", hash = "sha256:d8d8b072799c36cadcdcc2b40d2a560ce09797ab3d2d596b2ad519a5e4df19ae"},
-    {file = "python_engineio-4.3.4-py3-none-any.whl", hash = "sha256:7454314a529bba20e745928601ffeaf101c1b5aca9a6c4e48ad397803d10ea0c"},
+    {file = "python-engineio-4.4.1.tar.gz", hash = "sha256:eb3663ecb300195926b526386f712dff84cd092c818fb7b62eeeda9160120c29"},
+    {file = "python_engineio-4.4.1-py3-none-any.whl", hash = "sha256:28ab67f94cba2e5f598cbb04428138fd6bb8b06d3478c939412da445f24f0773"},
 ]
 ]
 
 
 [package.extras]
 [package.extras]
@@ -1000,14 +981,14 @@ six = ">=1.4.0"
 
 
 [[package]]
 [[package]]
 name = "python-socketio"
 name = "python-socketio"
-version = "5.7.2"
+version = "5.8.0"
 description = "Socket.IO server and client for Python"
 description = "Socket.IO server and client for Python"
 category = "main"
 category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
-    {file = "python-socketio-5.7.2.tar.gz", hash = "sha256:92395062d9db3c13d30e7cdedaa0e1330bba78505645db695415f9a3c628d097"},
-    {file = "python_socketio-5.7.2-py3-none-any.whl", hash = "sha256:d9a9f047e6fdd306c852fbac36516f4b495c2096f8ad9ceb8803b8e5ff5622e3"},
+    {file = "python-socketio-5.8.0.tar.gz", hash = "sha256:e714f4dddfaaa0cb0e37a1e2deef2bb60590a5b9fea9c343dd8ca5e688416fd9"},
+    {file = "python_socketio-5.8.0-py3-none-any.whl", hash = "sha256:7adb8867aac1c2929b9c1429f1c02e12ca4c36b67c807967393e367dfbb01441"},
 ]
 ]
 
 
 [package.dependencies]
 [package.dependencies]
@@ -1020,14 +1001,14 @@ client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
 
 
 [[package]]
 [[package]]
 name = "pytz"
 name = "pytz"
-version = "2022.7.1"
+version = "2023.3"
 description = "World timezone definitions, modern and historical"
 description = "World timezone definitions, modern and historical"
 category = "dev"
 category = "dev"
 optional = false
 optional = false
 python-versions = "*"
 python-versions = "*"
 files = [
 files = [
-    {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"},
-    {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"},
+    {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"},
+    {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"},
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -1082,18 +1063,18 @@ files = [
 
 
 [[package]]
 [[package]]
 name = "redis"
 name = "redis"
-version = "4.5.1"
+version = "4.5.4"
 description = "Python client for Redis database and key-value store"
 description = "Python client for Redis database and key-value store"
 category = "main"
 category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "redis-4.5.1-py3-none-any.whl", hash = "sha256:5deb072d26e67d2be1712603bfb7947ec3431fb0eec9c578994052e33035af6d"},
-    {file = "redis-4.5.1.tar.gz", hash = "sha256:1eec3741cda408d3a5f84b78d089c8b8d895f21b3b050988351e925faf202864"},
+    {file = "redis-4.5.4-py3-none-any.whl", hash = "sha256:2c19e6767c474f2e85167909061d525ed65bea9301c0770bb151e041b7ac89a2"},
+    {file = "redis-4.5.4.tar.gz", hash = "sha256:73ec35da4da267d6847e47f68730fdd5f62e2ca69e3ef5885c6a78a9374c3893"},
 ]
 ]
 
 
 [package.dependencies]
 [package.dependencies]
-async-timeout = ">=4.0.2"
+async-timeout = {version = ">=4.0.2", markers = "python_version <= \"3.11.2\""}
 importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""}
 importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""}
 typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
 typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
 
 
@@ -1167,14 +1148,14 @@ files = [
 
 
 [[package]]
 [[package]]
 name = "setuptools"
 name = "setuptools"
-version = "67.5.1"
+version = "67.7.2"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 category = "main"
 category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "setuptools-67.5.1-py3-none-any.whl", hash = "sha256:1c39d42bda4cb89f7fdcad52b6762e3c309ec8f8715b27c684176b7d71283242"},
-    {file = "setuptools-67.5.1.tar.gz", hash = "sha256:15136a251127da2d2e77ac7a1bc231eb504654f7e3346d93613a13f2e2787535"},
+    {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"},
+    {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"},
 ]
 ]
 
 
 [package.extras]
 [package.extras]
@@ -1284,14 +1265,14 @@ sqlcipher = ["sqlcipher3-binary"]
 
 
 [[package]]
 [[package]]
 name = "sqlalchemy2-stubs"
 name = "sqlalchemy2-stubs"
-version = "0.0.2a32"
+version = "0.0.2a34"
 description = "Typing Stubs for SQLAlchemy 1.4"
 description = "Typing Stubs for SQLAlchemy 1.4"
 category = "main"
 category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
-    {file = "sqlalchemy2-stubs-0.0.2a32.tar.gz", hash = "sha256:2a2cfab71d35ac63bf21ad841d8610cd93a3bd4c6562848c538fa975585c2739"},
-    {file = "sqlalchemy2_stubs-0.0.2a32-py3-none-any.whl", hash = "sha256:7f5fb30b0cf7c6b74c50c1d94df77ff32007afee8d80499752eb3fedffdbdfb8"},
+    {file = "sqlalchemy2-stubs-0.0.2a34.tar.gz", hash = "sha256:2432137ab2fde1a608df4544f6712427b0b7ff25990cfbbc5a9d1db6c8c6f489"},
+    {file = "sqlalchemy2_stubs-0.0.2a34-py3-none-any.whl", hash = "sha256:a313220ac793404349899faf1272e821a62dbe1d3a029bd444faa8d3e966cd07"},
 ]
 ]
 
 
 [package.dependencies]
 [package.dependencies]
@@ -1461,24 +1442,24 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
 
 
 [[package]]
 [[package]]
 name = "virtualenv"
 name = "virtualenv"
-version = "20.21.0"
+version = "20.23.0"
 description = "Virtual Python Environment builder"
 description = "Virtual Python Environment builder"
 category = "dev"
 category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "virtualenv-20.21.0-py3-none-any.whl", hash = "sha256:31712f8f2a17bd06234fa97fdf19609e789dd4e3e4bf108c3da71d710651adbc"},
-    {file = "virtualenv-20.21.0.tar.gz", hash = "sha256:f50e3e60f990a0757c9b68333c9fdaa72d7188caa417f96af9e52407831a3b68"},
+    {file = "virtualenv-20.23.0-py3-none-any.whl", hash = "sha256:6abec7670e5802a528357fdc75b26b9f57d5d92f29c5462ba0fbe45feacc685e"},
+    {file = "virtualenv-20.23.0.tar.gz", hash = "sha256:a85caa554ced0c0afbd0d638e7e2d7b5f92d23478d05d17a76daeac8f279f924"},
 ]
 ]
 
 
 [package.dependencies]
 [package.dependencies]
 distlib = ">=0.3.6,<1"
 distlib = ">=0.3.6,<1"
-filelock = ">=3.4.1,<4"
-platformdirs = ">=2.4,<4"
+filelock = ">=3.11,<4"
+platformdirs = ">=3.2,<4"
 
 
 [package.extras]
 [package.extras]
-docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"]
-test = ["covdefaults (>=2.2.2)", "coverage (>=7.1)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23)", "pytest (>=7.2.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)"]
+docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"]
+test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.7.1)", "time-machine (>=2.9)"]
 
 
 [[package]]
 [[package]]
 name = "watchdog"
 name = "watchdog"

+ 44 - 38
pynecone/app.py

@@ -24,7 +24,7 @@ from pynecone.route import (
     get_route_args,
     get_route_args,
     verify_route_validity,
     verify_route_validity,
 )
 )
-from pynecone.state import DefaultState, Delta, State, StateManager, StateUpdate
+from pynecone.state import DefaultState, State, StateManager, StateUpdate
 from pynecone.utils import format, types
 from pynecone.utils import format, types
 
 
 # Define custom types.
 # Define custom types.
@@ -62,8 +62,8 @@ class App(Base):
     # Middleware to add to the app.
     # Middleware to add to the app.
     middleware: List[Middleware] = []
     middleware: List[Middleware] = []
 
 
-    # Event handlers to trigger when a page loads.
-    load_events: Dict[str, Union[EventHandler, List[EventHandler]]] = {}
+    # List of event handlers to trigger when a page loads.
+    load_events: Dict[str, List[EventHandler]] = {}
 
 
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         """Initialize the app.
         """Initialize the app.
@@ -149,16 +149,14 @@ class App(Base):
             allow_origins=["*"],
             allow_origins=["*"],
         )
         )
 
 
-    async def preprocess(
-        self, state: State, event: Event
-    ) -> Optional[Union[StateUpdate, List[StateUpdate]]]:
+    async def preprocess(self, state: State, event: Event) -> Optional[StateUpdate]:
         """Preprocess the event.
         """Preprocess the event.
 
 
         This is where middleware can modify the event before it is processed.
         This is where middleware can modify the event before it is processed.
         Each middleware is called in the order it was added to the app.
         Each middleware is called in the order it was added to the app.
 
 
-        If a middleware returns a delta, the event is not processed and the
-        delta is returned.
+        If a middleware returns an update, the event is not processed and the
+        update is returned.
 
 
         Args:
         Args:
             state: The state to preprocess.
             state: The state to preprocess.
@@ -176,35 +174,33 @@ class App(Base):
                 return out  # type: ignore
                 return out  # type: ignore
 
 
     async def postprocess(
     async def postprocess(
-        self, state: State, event: Event, delta: Delta
-    ) -> Optional[Delta]:
+        self, state: State, event: Event, update: StateUpdate
+    ) -> StateUpdate:
         """Postprocess the event.
         """Postprocess the event.
 
 
         This is where middleware can modify the delta after it is processed.
         This is where middleware can modify the delta after it is processed.
         Each middleware is called in the order it was added to the app.
         Each middleware is called in the order it was added to the app.
 
 
-        If a middleware returns a delta, the delta is not processed and the
-        delta is returned.
-
         Args:
         Args:
             state: The state to postprocess.
             state: The state to postprocess.
             event: The event to postprocess.
             event: The event to postprocess.
-            delta: The delta to postprocess.
+            update: The current state update.
 
 
         Returns:
         Returns:
-            An optional state to return.
+            The state update to return.
         """
         """
         for middleware in self.middleware:
         for middleware in self.middleware:
             if asyncio.iscoroutinefunction(middleware.postprocess):
             if asyncio.iscoroutinefunction(middleware.postprocess):
                 out = await middleware.postprocess(
                 out = await middleware.postprocess(
-                    app=self, state=state, event=event, delta=delta
+                    app=self, state=state, event=event, update=update
                 )
                 )
             else:
             else:
                 out = middleware.postprocess(
                 out = middleware.postprocess(
-                    app=self, state=state, event=event, delta=delta
+                    app=self, state=state, event=event, update=update
                 )
                 )
             if out is not None:
             if out is not None:
                 return out  # type: ignore
                 return out  # type: ignore
+        return update
 
 
     def add_middleware(self, middleware: Middleware, index: Optional[int] = None):
     def add_middleware(self, middleware: Middleware, index: Optional[int] = None):
         """Add middleware to the app.
         """Add middleware to the app.
@@ -289,9 +285,26 @@ class App(Base):
         self._check_routes_conflict(route)
         self._check_routes_conflict(route)
         self.pages[route] = component
         self.pages[route] = component
 
 
+        # Add the load events.
         if on_load:
         if on_load:
+            if not isinstance(on_load, list):
+                on_load = [on_load]
             self.load_events[route] = on_load
             self.load_events[route] = on_load
 
 
+    def get_load_events(self, route: str) -> List[EventHandler]:
+        """Get the load events for a route.
+
+        Args:
+            route: The route to get the load events for.
+
+        Returns:
+            The load events for the route.
+        """
+        route = route.lstrip("/")
+        if route == "":
+            route = constants.INDEX_ROUTE
+        return self.load_events.get(route, [])
+
     def _check_routes_conflict(self, new_route: str):
     def _check_routes_conflict(self, new_route: str):
         """Verify if there is any conflict between the new route and any existing route.
         """Verify if there is any conflict between the new route and any existing route.
 
 
@@ -411,7 +424,7 @@ class App(Base):
 
 
 async def process(
 async def process(
     app: App, event: Event, sid: str, headers: Dict, client_ip: str
     app: App, event: Event, sid: str, headers: Dict, client_ip: str
-) -> List[StateUpdate]:
+) -> StateUpdate:
     """Process an event.
     """Process an event.
 
 
     Args:
     Args:
@@ -444,27 +457,21 @@ async def process(
         substate.router_data = state.router_data
         substate.router_data = state.router_data
 
 
     # Preprocess the event.
     # Preprocess the event.
-    pre = await app.preprocess(state, event)
-    if isinstance(pre, StateUpdate):
-        return [pre]
-    updates = pre
+    update = await app.preprocess(state, event)
 
 
-    # Apply the event to the state.
-    if updates is None:
-        updates = [await state._process(event)]
-        app.state_manager.set_state(event.token, state)
+    # Only process the event if there is no update.
+    if update is None:
+        # Apply the event to the state.
+        update = await state._process(event)
 
 
-    # Postprocess the event.
-    post_list = []
-    for update in updates:
-        post = await app.postprocess(state, event, update.delta)  # type: ignore
-        post_list.append(post) if post else None
+        # Postprocess the event.
+        update = await app.postprocess(state, event, update)
 
 
-    if len(post_list) > 0:
-        return [StateUpdate(delta=post) for post in post_list]
+    # Update the state.
+    app.state_manager.set_state(event.token, state)
 
 
-    # Return the updates.
-    return updates
+    # Return the update.
+    return update
 
 
 
 
 async def ping() -> str:
 async def ping() -> str:
@@ -598,11 +605,10 @@ class EventNamespace(AsyncNamespace):
         client_ip = environ["REMOTE_ADDR"]
         client_ip = environ["REMOTE_ADDR"]
 
 
         # Process the events.
         # Process the events.
-        updates = await process(self.app, event, sid, headers, client_ip)
+        update = await process(self.app, event, sid, headers, client_ip)
 
 
         # Emit the event.
         # Emit the event.
-        for update in updates:
-            await self.emit(str(constants.SocketEvent.EVENT), update.json(), to=sid)  # type: ignore
+        await self.emit(str(constants.SocketEvent.EVENT), update.json(), to=sid)  # type: ignore
 
 
     async def on_ping(self, sid):
     async def on_ping(self, sid):
         """Event for testing the API endpoint.
         """Event for testing the API endpoint.

+ 2 - 0
pynecone/constants.py

@@ -106,6 +106,8 @@ STATE = "state"
 EVENTS = "events"
 EVENTS = "events"
 # The name of the initial hydrate event.
 # The name of the initial hydrate event.
 HYDRATE = "hydrate"
 HYDRATE = "hydrate"
+# The name of the is_hydrated variable.
+IS_HYDRATED = "is_hydrated"
 # The name of the index page.
 # The name of the index page.
 INDEX_ROUTE = "index"
 INDEX_ROUTE = "index"
 # The name of the document root page.
 # The name of the document root page.

+ 5 - 7
pynecone/event.py

@@ -23,7 +23,7 @@ class Event(Base):
     router_data: Dict[str, Any] = {}
     router_data: Dict[str, Any] = {}
 
 
     # The event payload.
     # The event payload.
-    payload: Dict[Any, Any] = {}
+    payload: Dict[str, Any] = {}
 
 
 
 
 class EventHandler(Base):
 class EventHandler(Base):
@@ -90,7 +90,7 @@ class EventSpec(Base):
     local_args: Tuple[Var, ...] = ()
     local_args: Tuple[Var, ...] = ()
 
 
     # The arguments to pass to the function.
     # The arguments to pass to the function.
-    args: Optional[Tuple[Tuple[Var, Var], ...]] = ()
+    args: Tuple[Tuple[Var, Var], ...] = ()
 
 
     # Whether to upload files.
     # Whether to upload files.
     upload: bool = False
     upload: bool = False
@@ -318,9 +318,7 @@ def call_event_fn(fn: Callable, arg: Var) -> List[EventSpec]:
     return events
     return events
 
 
 
 
-def get_handler_args(
-    event_spec: EventSpec, arg: Var
-) -> Optional[Tuple[Tuple[Var, Var], ...]]:
+def get_handler_args(event_spec: EventSpec, arg: Var) -> Tuple[Tuple[Var, Var], ...]:
     """Get the handler args for the given event spec.
     """Get the handler args for the given event spec.
 
 
     Args:
     Args:
@@ -335,7 +333,7 @@ def get_handler_args(
     return (
     return (
         event_spec.args
         event_spec.args
         if len(args) > 2
         if len(args) > 2
-        else (((Var.create_safe(args[1]), arg),) if len(args) == 2 else None)
+        else (((Var.create_safe(args[1]), arg),) if len(args) == 2 else tuple())
     )
     )
 
 
 
 
@@ -369,7 +367,7 @@ def fix_events(
             e = e()
             e = e()
         assert isinstance(e, EventSpec), f"Unexpected event type, {type(e)}."
         assert isinstance(e, EventSpec), f"Unexpected event type, {type(e)}."
         name = format.format_event_handler(e.handler)
         name = format.format_event_handler(e.handler)
-        payload = {k.name: v.name for k, v in e.args} if e.args else {}
+        payload = {k.name: v.name for k, v in e.args}
 
 
         # Create an event and append it to the list.
         # Create an event and append it to the list.
         out.append(
         out.append(

+ 16 - 65
pynecone/middleware/hydrate_middleware.py

@@ -1,10 +1,10 @@
 """Middleware to hydrate the state."""
 """Middleware to hydrate the state."""
 from __future__ import annotations
 from __future__ import annotations
 
 
-from typing import TYPE_CHECKING, Dict, List, Optional, Union
+from typing import TYPE_CHECKING, Optional
 
 
 from pynecone import constants
 from pynecone import constants
-from pynecone.event import Event, EventHandler, get_hydrate_event
+from pynecone.event import Event, fix_events, get_hydrate_event
 from pynecone.middleware.middleware import Middleware
 from pynecone.middleware.middleware import Middleware
 from pynecone.state import State, StateUpdate
 from pynecone.state import State, StateUpdate
 from pynecone.utils import format
 from pynecone.utils import format
@@ -13,10 +13,7 @@ if TYPE_CHECKING:
     from pynecone.app import App
     from pynecone.app import App
 
 
 
 
-IS_HYDRATED = "is_hydrated"
-
-
-State.add_var(IS_HYDRATED, type_=bool, default_value=False)
+State.add_var(constants.IS_HYDRATED, type_=bool, default_value=False)
 
 
 
 
 class HydrateMiddleware(Middleware):
 class HydrateMiddleware(Middleware):
@@ -24,7 +21,7 @@ class HydrateMiddleware(Middleware):
 
 
     async def preprocess(
     async def preprocess(
         self, app: App, state: State, event: Event
         self, app: App, state: State, event: Event
-    ) -> Optional[Union[StateUpdate, List[StateUpdate]]]:
+    ) -> Optional[StateUpdate]:
         """Preprocess the event.
         """Preprocess the event.
 
 
         Args:
         Args:
@@ -35,65 +32,19 @@ class HydrateMiddleware(Middleware):
         Returns:
         Returns:
             An optional delta or list of state updates to return.
             An optional delta or list of state updates to return.
         """
         """
-        if event.name == get_hydrate_event(state):
-            route = event.router_data.get(constants.RouteVar.PATH, "")
-            if route == "/":
-                load_event = app.load_events.get(constants.INDEX_ROUTE)
-            elif route:
-                load_event = app.load_events.get(route.lstrip("/"))
-            else:
-                load_event = None
-
-            updates = []
+        # If this is not the hydrate event, return None
+        if event.name != get_hydrate_event(state):
+            return None
 
 
-            # first get the initial state
-            delta = format.format_state({state.get_name(): state.dict()})
-            if delta:
-                updates.append(StateUpdate(delta=delta))
+        # Get the initial state.
+        delta = format.format_state({state.get_name(): state.dict()})
 
 
-            # then apply changes from on_load event handlers on top of that
-            if load_event:
-                if not isinstance(load_event, List):
-                    load_event = [load_event]
-                for single_event in load_event:
-                    updates.append(
-                        await self.execute_load_event(
-                            state, single_event, event.token, event.payload
-                        )
-                    )
-            # extra message telling the client state that hydration is complete
-            updates.append(
-                StateUpdate(
-                    delta=format.format_state({state.get_name(): {IS_HYDRATED: True}})
-                )
-            )
+        # Get the route for on_load events.
+        route = event.router_data.get(constants.RouteVar.PATH, "")
 
 
-            return updates
-
-    async def execute_load_event(
-        self, state: State, load_event: EventHandler, token: str, payload: Dict
-    ) -> StateUpdate:
-        """Execute single load event.
-
-        Args:
-            state: The client state.
-            load_event: A single load event to execute.
-            token: Client token
-            payload: The event payload
-
-        Returns:
-            A state Update.
-
-        Raises:
-            ValueError: If the state value is None.
-        """
-        substate_path = format.format_event_handler(load_event).split(".")
-        ex_state = state.get_substate(substate_path[:-1])
-        if not ex_state:
-            raise ValueError(
-                "The value of state cannot be None when processing an on-load event."
-            )
+        # Add the on_load events and set is_hydrated to True.
+        events = [*app.get_load_events(route), type(state).set_is_hydrated(True)]  # type: ignore
+        events = fix_events(events, event.token)
 
 
-        return await state._process_event(
-            handler=load_event, state=ex_state, payload=payload, token=token
-        )
+        # Return the state update.
+        return StateUpdate(delta=delta, events=events)

+ 6 - 4
pynecone/middleware/logging_middleware.py

@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
 
 
 from pynecone.event import Event
 from pynecone.event import Event
 from pynecone.middleware.middleware import Middleware
 from pynecone.middleware.middleware import Middleware
-from pynecone.state import Delta, State
+from pynecone.state import State, StateUpdate
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from pynecone.app import App
     from pynecone.app import App
@@ -24,13 +24,15 @@ class LoggingMiddleware(Middleware):
         """
         """
         print(f"Event {event}")
         print(f"Event {event}")
 
 
-    async def postprocess(self, app: App, state: State, event: Event, delta: Delta):
+    async def postprocess(
+        self, app: App, state: State, event: Event, update: StateUpdate
+    ):
         """Postprocess the event.
         """Postprocess the event.
 
 
         Args:
         Args:
             app: The app to apply the middleware to.
             app: The app to apply the middleware to.
             state: The client state.
             state: The client state.
             event: The event to postprocess.
             event: The event to postprocess.
-            delta: The delta to postprocess.
+            update: The current state update.
         """
         """
-        print(f"Delta {delta}")
+        print(f"Update {update}")

+ 8 - 8
pynecone/middleware/middleware.py

@@ -2,11 +2,11 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
 from abc import ABC
 from abc import ABC
-from typing import TYPE_CHECKING, List, Optional, Union
+from typing import TYPE_CHECKING, Optional
 
 
 from pynecone.base import Base
 from pynecone.base import Base
 from pynecone.event import Event
 from pynecone.event import Event
-from pynecone.state import Delta, State, StateUpdate
+from pynecone.state import State, StateUpdate
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from pynecone.app import App
     from pynecone.app import App
@@ -17,7 +17,7 @@ class Middleware(Base, ABC):
 
 
     async def preprocess(
     async def preprocess(
         self, app: App, state: State, event: Event
         self, app: App, state: State, event: Event
-    ) -> Optional[Union[StateUpdate, List[StateUpdate]]]:
+    ) -> Optional[StateUpdate]:
         """Preprocess the event.
         """Preprocess the event.
 
 
         Args:
         Args:
@@ -26,22 +26,22 @@ class Middleware(Base, ABC):
             event: The event to preprocess.
             event: The event to preprocess.
 
 
         Returns:
         Returns:
-            An optional state to return.
+            An optional state update to return.
         """
         """
         return None
         return None
 
 
     async def postprocess(
     async def postprocess(
-        self, app: App, state: State, event: Event, delta
-    ) -> Optional[Delta]:
+        self, app: App, state: State, event: Event, update: StateUpdate
+    ) -> StateUpdate:
         """Postprocess the event.
         """Postprocess the event.
 
 
         Args:
         Args:
             app: The app.
             app: The app.
             state: The client state.
             state: The client state.
             event: The event to postprocess.
             event: The event to postprocess.
-            delta: The delta to postprocess.
+            update: The current state update.
 
 
         Returns:
         Returns:
             An optional state to return.
             An optional state to return.
         """
         """
-        return None
+        return update

+ 7 - 14
pynecone/utils/format.py

@@ -286,20 +286,13 @@ def format_event(event_spec: EventSpec) -> str:
     Returns:
     Returns:
         The compiled event.
         The compiled event.
     """
     """
-    args = (
-        ",".join(
-            [
-                ":".join(
-                    (
-                        name.name,
-                        json.dumps(val.name) if val.is_string else val.full_name,
-                    )
-                )
-                for name, val in event_spec.args
-            ]
-        )
-        if event_spec.args is not None
-        else ""
+    args = ",".join(
+        [
+            ":".join(
+                (name.name, json.dumps(val.name) if val.is_string else val.full_name)
+            )
+            for name, val in event_spec.args
+        ]
     )
     )
     return f"E(\"{format_event_handler(event_spec.handler)}\", {wrap(args, '{')})"
     return f"E(\"{format_event_handler(event_spec.handler)}\", {wrap(args, '{')})"
 
 

+ 1 - 1
pyproject.toml

@@ -1,6 +1,6 @@
 [tool.poetry]
 [tool.poetry]
 name = "pynecone"
 name = "pynecone"
-version = "0.1.28"
+version = "0.1.29"
 description = "Web apps in pure Python."
 description = "Web apps in pure Python."
 license = "Apache-2.0"
 license = "Apache-2.0"
 authors = [
 authors = [

+ 50 - 30
tests/middleware/test_hydrate_middleware.py

@@ -1,10 +1,11 @@
-from typing import Any, Dict, List
+from typing import Any, Dict
 
 
 import pytest
 import pytest
 
 
 from pynecone.app import App
 from pynecone.app import App
-from pynecone.middleware.hydrate_middleware import IS_HYDRATED, HydrateMiddleware
-from pynecone.state import State
+from pynecone.constants import IS_HYDRATED
+from pynecone.middleware.hydrate_middleware import HydrateMiddleware
+from pynecone.state import State, StateUpdate
 
 
 
 
 def exp_is_hydrated(state: State) -> Dict[str, Any]:
 def exp_is_hydrated(state: State) -> Dict[str, Any]:
@@ -16,7 +17,7 @@ def exp_is_hydrated(state: State) -> Dict[str, Any]:
     Returns:
     Returns:
         dict similar to that returned by `State.get_delta` with IS_HYDRATED: True
         dict similar to that returned by `State.get_delta` with IS_HYDRATED: True
     """
     """
-    return {state.get_name(): {IS_HYDRATED: True}}
+    return {state.get_name(): {IS_HYDRATED: "true"}}
 
 
 
 
 class TestState(State):
 class TestState(State):
@@ -77,33 +78,41 @@ def hydrate_middleware() -> HydrateMiddleware:
 
 
 @pytest.mark.asyncio
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(
-    "state, expected, event_fixture",
+    "State, expected, event_fixture",
     [
     [
         (TestState, {"test_state": {"num": 1}}, "event1"),
         (TestState, {"test_state": {"num": 1}}, "event1"),
         (TestState2, {"test_state2": {"num": 1}}, "event2"),
         (TestState2, {"test_state2": {"num": 1}}, "event2"),
         (TestState3, {"test_state3": {"num": 1}}, "event3"),
         (TestState3, {"test_state3": {"num": 1}}, "event3"),
     ],
     ],
 )
 )
-async def test_preprocess(state, hydrate_middleware, request, event_fixture, expected):
+async def test_preprocess(State, hydrate_middleware, request, event_fixture, expected):
     """Test that a state hydrate event is processed correctly.
     """Test that a state hydrate event is processed correctly.
 
 
     Args:
     Args:
-        state: state to process event
+        State: state to process event
         hydrate_middleware: instance of HydrateMiddleware
         hydrate_middleware: instance of HydrateMiddleware
         request: pytest fixture request
         request: pytest fixture request
         event_fixture: The event fixture(an Event)
         event_fixture: The event fixture(an Event)
         expected: expected delta
         expected: expected delta
     """
     """
-    app = App(state=state, load_events={"index": state.test_handler})
+    app = App(state=State, load_events={"index": [State.test_handler]})
+    state = State()
 
 
-    result = await hydrate_middleware.preprocess(
-        app=app, event=request.getfixturevalue(event_fixture), state=state()
+    update = await hydrate_middleware.preprocess(
+        app=app, event=request.getfixturevalue(event_fixture), state=state
     )
     )
-    assert isinstance(result, List)
-    assert len(result) == 3
-    assert result[0].delta == {state().get_name(): state().dict()}
-    assert result[1].delta == expected
-    assert result[2].delta == exp_is_hydrated(state())
+    assert isinstance(update, StateUpdate)
+    assert update.delta == {state.get_name(): state.dict()}
+    events = update.events
+    assert len(events) == 2
+
+    # Apply the on_load event.
+    update = await state._process(events[0])
+    assert update.delta == expected
+
+    # Apply the hydrate event.
+    update = await state._process(events[1])
+    assert update.delta == exp_is_hydrated(state)
 
 
 
 
 @pytest.mark.asyncio
 @pytest.mark.asyncio
@@ -118,16 +127,23 @@ async def test_preprocess_multiple_load_events(hydrate_middleware, event1):
         state=TestState,
         state=TestState,
         load_events={"index": [TestState.test_handler, TestState.test_handler]},
         load_events={"index": [TestState.test_handler, TestState.test_handler]},
     )
     )
+    state = TestState()
 
 
-    result = await hydrate_middleware.preprocess(
-        app=app, event=event1, state=TestState()
-    )
-    assert isinstance(result, List)
-    assert len(result) == 4
-    assert result[0].delta == {"test_state": TestState().dict()}
-    assert result[1].delta == {"test_state": {"num": 1}}
-    assert result[2].delta == {"test_state": {"num": 2}}
-    assert result[3].delta == exp_is_hydrated(TestState())
+    update = await hydrate_middleware.preprocess(app=app, event=event1, state=state)
+    assert isinstance(update, StateUpdate)
+    assert update.delta == {"test_state": state.dict()}
+    assert len(update.events) == 3
+
+    # Apply the events.
+    events = update.events
+    update = await state._process(events[0])
+    assert update.delta == {"test_state": {"num": 1}}
+
+    update = await state._process(events[1])
+    assert update.delta == {"test_state": {"num": 2}}
+
+    update = await state._process(events[2])
+    assert update.delta == exp_is_hydrated(state)
 
 
 
 
 @pytest.mark.asyncio
 @pytest.mark.asyncio
@@ -138,12 +154,16 @@ async def test_preprocess_no_events(hydrate_middleware, event1):
         hydrate_middleware: instance of HydrateMiddleware
         hydrate_middleware: instance of HydrateMiddleware
         event1: an Event.
         event1: an Event.
     """
     """
-    result = await hydrate_middleware.preprocess(
+    state = TestState()
+    update = await hydrate_middleware.preprocess(
         app=App(state=TestState),
         app=App(state=TestState),
         event=event1,
         event=event1,
-        state=TestState(),
+        state=state,
     )
     )
-    assert isinstance(result, List)
-    assert len(result) == 2
-    assert result[0].delta == {"test_state": TestState().dict()}
-    assert result[1].delta == exp_is_hydrated(TestState())
+    assert isinstance(update, StateUpdate)
+    assert update.delta == {"test_state": state.dict()}
+    assert len(update.events) == 1
+    assert isinstance(update, StateUpdate)
+
+    update = await state._process(update.events[0])
+    assert update.delta == exp_is_hydrated(state)

+ 1 - 2
tests/test_state.py

@@ -4,9 +4,8 @@ import pytest
 from plotly.graph_objects import Figure
 from plotly.graph_objects import Figure
 
 
 from pynecone.base import Base
 from pynecone.base import Base
-from pynecone.constants import RouteVar
+from pynecone.constants import IS_HYDRATED, RouteVar
 from pynecone.event import Event, EventHandler
 from pynecone.event import Event, EventHandler
-from pynecone.middleware.hydrate_middleware import IS_HYDRATED
 from pynecone.state import State
 from pynecone.state import State
 from pynecone.utils import format
 from pynecone.utils import format
 from pynecone.var import BaseVar, ComputedVar
 from pynecone.var import BaseVar, ComputedVar