From c3e66442217d970fa25f4682415e1e173d6a0001 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 28 Feb 2025 18:48:27 -0800 Subject: [PATCH 01/14] json patch --- poetry.lock | 238 ++++++++---------------- pyproject.toml | 1 + reflex/.templates/web/utils/state.js | 47 +++-- reflex/app.py | 2 +- reflex/compiler/utils.py | 8 +- reflex/config.py | 3 + reflex/constants/installer.py | 1 + reflex/middleware/hydrate_middleware.py | 10 +- reflex/state.py | 141 +++++++++++++- tests/units/test_app.py | 51 ++--- tests/units/test_state.py | 91 +++++---- 11 files changed, 344 insertions(+), 249 deletions(-) diff --git a/poetry.lock b/poetry.lock index 139ef29e076..1993a7fbc80 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "alembic" @@ -7,7 +7,6 @@ description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "alembic-1.14.1-py3-none-any.whl", hash = "sha256:1acdd7a3a478e208b0503cd73614d5e4c6efafa4e73518bb60e4f2846a37b1c5"}, {file = "alembic-1.14.1.tar.gz", hash = "sha256:496e888245a53adf1498fcab31713a469c65836f8de76e01399aa1c3e90dd213"}, @@ -19,7 +18,7 @@ SQLAlchemy = ">=1.3.0" typing-extensions = ">=4" [package.extras] -tz = ["backports.zoneinfo", "tzdata"] +tz = ["backports.zoneinfo ; python_version < \"3.9\"", "tzdata"] [[package]] name = "annotated-types" @@ -28,7 +27,6 @@ description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -41,7 +39,6 @@ description = "High level compatibility layer for multiple asynchronous event lo optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, @@ -55,7 +52,7 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] [[package]] @@ -78,7 +75,6 @@ description = "Enhance the standard unittest package with features for testing a optional = false python-versions = ">=3.5" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, @@ -91,19 +87,18 @@ description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "backports-tarfile" @@ -112,7 +107,7 @@ description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\")" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version <= \"3.11\"" files = [ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"}, @@ -129,7 +124,6 @@ description = "The bidirectional mapping library for Python." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"}, {file = "bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71"}, @@ -142,7 +136,6 @@ description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"}, {file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"}, @@ -157,7 +150,7 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [package.extras] docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] -test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"] +test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0) ; python_version < \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.11\"", "setuptools (>=67.8.0) ; python_version >= \"3.12\"", "wheel (>=0.36.0)"] typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"] uv = ["uv (>=0.1.18)"] virtualenv = ["virtualenv (>=20.0.35)"] @@ -169,7 +162,6 @@ description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -251,7 +243,7 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] -markers = {main = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and platform_python_implementation != \"PyPy\" or python_version >= \"3.12\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and platform_python_implementation != \"PyPy\"", dev = "python_version <= \"3.11\" or python_version >= \"3.12\""} +markers = {main = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and sys_platform == \"linux\" and platform_python_implementation != \"PyPy\""} [package.dependencies] pycparser = "*" @@ -263,7 +255,6 @@ description = "Validate configuration and produce human readable error messages. optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -276,7 +267,6 @@ description = "The Real First Universal Charset Detector. Open, modern and activ optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -379,7 +369,6 @@ description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -399,7 +388,7 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "python_version <= \"3.11\" and platform_system == \"Windows\" or python_version <= \"3.11\" and os_name == \"nt\" or python_version >= \"3.12\" and platform_system == \"Windows\" or python_version >= \"3.12\" and os_name == \"nt\"", dev = "python_version <= \"3.11\" and sys_platform == \"win32\" or python_version >= \"3.12\" and sys_platform == \"win32\""} +markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", dev = "sys_platform == \"win32\""} [[package]] name = "coverage" @@ -408,7 +397,6 @@ description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, @@ -479,7 +467,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cryptography" @@ -488,7 +476,7 @@ description = "cryptography is a package which provides cryptographic recipes an optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" or python_version >= \"3.12\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\"" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and sys_platform == \"linux\"" files = [ {file = "cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009"}, {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f"}, @@ -527,10 +515,10 @@ files = [ cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] -pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""] +pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.1)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] @@ -543,7 +531,6 @@ description = "A utility for ensuring Google-style docstrings stay up to date wi optional = false python-versions = ">=3.6,<4.0" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"}, {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, @@ -556,7 +543,6 @@ description = "serialize all of Python" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, @@ -573,7 +559,6 @@ description = "Distribution utilities" optional = false python-versions = "*" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -586,7 +571,7 @@ description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" groups = ["main"] -markers = "python_version <= \"3.11\" and sys_platform == \"linux\" or python_version >= \"3.12\" and sys_platform == \"linux\"" +markers = "sys_platform == \"linux\"" files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -599,7 +584,6 @@ description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, @@ -628,7 +612,6 @@ description = "FastAPI framework, high performance, easy to learn, fast to code, optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "fastapi-0.115.8-py3-none-any.whl", hash = "sha256:753a96dd7e036b34eeef8babdfcfe3f28ff79648f86551eb36bfc1b0bf4a8cbf"}, {file = "fastapi-0.115.8.tar.gz", hash = "sha256:0ce9111231720190473e222cdf0f07f7206ad7e53ea02beb1d2dc36e2f0741e9"}, @@ -650,7 +633,6 @@ description = "A platform independent file lock." optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, @@ -659,7 +641,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "greenlet" @@ -743,7 +725,7 @@ files = [ {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, ] -markers = {main = "python_version <= \"3.11\" 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\") or python_version >= \"3.12\" and python_version < \"3.14\" 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\")", dev = "python_version <= \"3.11\" or python_version >= \"3.12\""} +markers = {main = "python_version < \"3.14\" 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\")"} [package.extras] docs = ["Sphinx", "furo"] @@ -756,7 +738,6 @@ description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -779,7 +760,6 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -792,7 +772,6 @@ description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, @@ -815,7 +794,6 @@ description = "The next generation HTTP client." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -828,7 +806,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -841,7 +819,6 @@ description = "A tool for generating OIDC identities" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "id-1.5.0-py3-none-any.whl", hash = "sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658"}, {file = "id-1.5.0.tar.gz", hash = "sha256:292cb8a49eacbbdbce97244f47a97b4c62540169c976552e497fd57df0734c1d"}, @@ -862,7 +839,6 @@ description = "File identification library for Python" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "identify-2.6.7-py2.py3-none-any.whl", hash = "sha256:155931cb617a401807b09ecec6635d6c692d180090a1cedca8ef7d58ba5b6aa0"}, {file = "identify-2.6.7.tar.gz", hash = "sha256:3fa266b42eba321ee0b2bb0936a6a6b9e36a1351cbb69055b3082f4193035684"}, @@ -878,7 +854,6 @@ description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -894,7 +869,7 @@ description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") or python_full_version < \"3.10.2\"" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version <= \"3.11\" or python_full_version < \"3.10.2\"" files = [ {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, @@ -904,12 +879,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -919,7 +894,6 @@ description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -932,7 +906,7 @@ description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") or python_version >= \"3.12\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\")" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, @@ -952,7 +926,7 @@ description = "Useful decorators and context managers" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") or python_version >= \"3.12\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\")" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, @@ -963,7 +937,7 @@ files = [ [package.extras] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] [[package]] name = "jaraco-functools" @@ -972,7 +946,7 @@ description = "Functools like those found in stdlib" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") or python_version >= \"3.12\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\")" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649"}, {file = "jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d"}, @@ -982,7 +956,7 @@ files = [ more-itertools = "*" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -996,7 +970,7 @@ description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" or python_version >= \"3.12\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\"" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and sys_platform == \"linux\"" files = [ {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, @@ -1004,7 +978,7 @@ files = [ [package.extras] test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] -trio = ["async_generator", "trio"] +trio = ["async_generator ; python_version == \"3.6\"", "trio"] [[package]] name = "jinja2" @@ -1013,7 +987,6 @@ description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, @@ -1025,6 +998,33 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["main"] +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "3.0.0" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, + {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, +] + [[package]] name = "keyring" version = "25.6.0" @@ -1032,7 +1032,7 @@ description = "Store and access your passwords safely." optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") or python_version >= \"3.12\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\")" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"}, {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"}, @@ -1048,7 +1048,7 @@ pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] completion = ["shtab (>=1.1.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] @@ -1063,7 +1063,6 @@ description = "Makes it easy to load subpackages and functions on demand." optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc"}, {file = "lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1"}, @@ -1084,7 +1083,6 @@ description = "A super-fast templating language that borrows the best ideas from optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "Mako-1.3.9-py3-none-any.whl", hash = "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1"}, {file = "mako-1.3.9.tar.gz", hash = "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac"}, @@ -1105,7 +1103,6 @@ description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -1131,7 +1128,6 @@ description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1203,7 +1199,6 @@ description = "Markdown URL utilities" optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -1216,7 +1211,7 @@ description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") or python_version >= \"3.12\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\")" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b"}, {file = "more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89"}, @@ -1229,7 +1224,6 @@ description = "Python binding to Ammonia HTML sanitizer Rust crate" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "nh3-0.2.20-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e1061a4ab6681f6bdf72b110eea0c4e1379d57c9de937db3be4202f7ad6043db"}, {file = "nh3-0.2.20-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb4254b1dac4a1ee49919a5b3f1caf9803ea8dada1816d9e8289e63d3cd0dd9a"}, @@ -1264,7 +1258,6 @@ description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -1277,7 +1270,6 @@ description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "numpy-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71"}, {file = "numpy-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787"}, @@ -1343,7 +1335,6 @@ description = "Capture the outcome of Python function calls." optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"}, {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"}, @@ -1359,7 +1350,6 @@ description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1372,7 +1362,6 @@ description = "Powerful data structures for data analysis, time series, and stat optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, @@ -1460,7 +1449,6 @@ description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"}, {file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"}, @@ -1540,7 +1528,7 @@ docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions"] +typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] @@ -1550,7 +1538,6 @@ description = "A small Python package for determining appropriate platform-speci optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -1568,7 +1555,6 @@ description = "A high-level API to automate web browsers" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "playwright-1.50.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:f36d754a6c5bd9bf7f14e8f57a2aea6fd08f39ca4c8476481b9c83e299531148"}, {file = "playwright-1.50.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:40f274384591dfd27f2b014596250b2250c843ed1f7f4ef5d2960ecb91b4961e"}, @@ -1590,7 +1576,6 @@ description = "An open-source, interactive data visualization library for Python optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, @@ -1607,7 +1592,6 @@ description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1624,7 +1608,6 @@ description = "A framework for managing and maintaining multi-language pre-commi optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"}, {file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"}, @@ -1644,7 +1627,6 @@ description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, @@ -1676,7 +1658,6 @@ description = "Get CPU info with pure Python" optional = false python-versions = "*" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"}, {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"}, @@ -1693,7 +1674,7 @@ files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] -markers = {main = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and platform_python_implementation != \"PyPy\" or python_version >= \"3.12\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and platform_python_implementation != \"PyPy\"", dev = "python_version <= \"3.11\" or python_version >= \"3.12\""} +markers = {main = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and sys_platform == \"linux\" and platform_python_implementation != \"PyPy\""} [[package]] name = "pydantic" @@ -1702,7 +1683,6 @@ description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, @@ -1715,7 +1695,7 @@ typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -1724,7 +1704,6 @@ description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, @@ -1838,7 +1817,6 @@ description = "A rough port of Node.js's EventEmitter to Python with a few trick optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyee-12.1.1-py3-none-any.whl", hash = "sha256:18a19c650556bb6b32b406d7f017c8f513aceed1ef7ca618fb65de7bd2d347ef"}, {file = "pyee-12.1.1.tar.gz", hash = "sha256:bbc33c09e2ff827f74191e3e5bbc6be7da02f627b7ec30d86f5ce1a6fb2424a3"}, @@ -1848,7 +1826,7 @@ files = [ typing-extensions = "*" [package.extras] -dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "pytest", "pytest-asyncio", "pytest-trio", "sphinx", "toml", "tox", "trio", "trio", "trio-typing", "twine", "twisted", "validate-pyproject[all]"] +dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "pytest", "pytest-asyncio ; python_version >= \"3.4\"", "pytest-trio ; python_version >= \"3.7\"", "sphinx", "toml", "tox", "trio", "trio ; python_version > \"3.6\"", "trio-typing ; python_version > \"3.6\"", "twine", "twisted", "validate-pyproject[all]"] [[package]] name = "pygments" @@ -1857,7 +1835,6 @@ description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, @@ -1873,7 +1850,6 @@ description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, @@ -1886,7 +1862,6 @@ description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyright-1.1.394-py3-none-any.whl", hash = "sha256:5f74cce0a795a295fb768759bbeeec62561215dea657edcaab48a932b031ddbb"}, {file = "pyright-1.1.394.tar.gz", hash = "sha256:56f2a3ab88c5214a451eb71d8f2792b7700434f841ea219119ade7f42ca93608"}, @@ -1908,7 +1883,6 @@ description = "A Python SOCKS client module. See https://github.com/Anorov/PySoc optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, @@ -1922,7 +1896,6 @@ description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -1946,7 +1919,6 @@ description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"}, {file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"}, @@ -1966,7 +1938,6 @@ description = "pytest plugin for URL based testing" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest_base_url-2.1.0-py3-none-any.whl", hash = "sha256:3ad15611778764d451927b2a53240c1a7a591b521ea44cebfe45849d2d2812e6"}, {file = "pytest_base_url-2.1.0.tar.gz", hash = "sha256:02748589a54f9e63fcbe62301d6b0496da0d10231b753e950c63e03aee745d45"}, @@ -1986,7 +1957,6 @@ description = "A ``pytest`` fixture for benchmarking code. It will group the tes optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-benchmark-5.1.0.tar.gz", hash = "sha256:9ea661cdc292e8231f7cd4c10b0319e56a2118e2c09d9f50e1b3d150d2aca105"}, {file = "pytest_benchmark-5.1.0-py3-none-any.whl", hash = "sha256:922de2dfa3033c227c96da942d1878191afa135a29485fb942e85dff1c592c89"}, @@ -2008,7 +1978,6 @@ description = "Pytest plugin to create CodSpeed benchmarks" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest_codspeed-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5165774424c7ab8db7e7acdb539763a0e5657996effefdf0664d7fd95158d34"}, {file = "pytest_codspeed-3.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bd55f92d772592c04a55209950c50880413ae46876e66bd349ef157075ca26c"}, @@ -2041,7 +2010,6 @@ description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, @@ -2061,7 +2029,6 @@ description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -2080,7 +2047,6 @@ description = "A pytest wrapper with fixtures for Playwright to automate web bro optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest_playwright-0.7.0-py3-none-any.whl", hash = "sha256:2516d0871fa606634bfe32afbcc0342d68da2dbff97fe3459849e9c428486da2"}, {file = "pytest_playwright-0.7.0.tar.gz", hash = "sha256:b3f2ea514bbead96d26376fac182f68dcd6571e7cb41680a89ff1673c05d60b6"}, @@ -2099,7 +2065,6 @@ description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2115,7 +2080,6 @@ description = "Engine.IO server and client for Python" optional = false python-versions = ">=3.6" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python_engineio-4.11.2-py3-none-any.whl", hash = "sha256:f0971ac4c65accc489154fe12efd88f53ca8caf04754c46a66e85f5102ef22ad"}, {file = "python_engineio-4.11.2.tar.gz", hash = "sha256:145bb0daceb904b4bb2d3eb2d93f7dbb7bb87a6a0c4f20a94cc8654dec977129"}, @@ -2136,7 +2100,6 @@ description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, @@ -2149,7 +2112,6 @@ description = "A Python slugify application that also handles Unicode" optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"}, {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"}, @@ -2168,7 +2130,6 @@ description = "Socket.IO server and client for Python" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python_socketio-5.12.1-py3-none-any.whl", hash = "sha256:24a0ea7cfff0e021eb28c68edbf7914ee4111bdf030b95e4d250c4dc9af7a386"}, {file = "python_socketio-5.12.1.tar.gz", hash = "sha256:0299ff1f470b676c09c1bfab1dead25405077d227b2c13cf217a34dadc68ba9c"}, @@ -2190,7 +2151,6 @@ description = "World timezone definitions, modern and historical" optional = false python-versions = "*" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, @@ -2203,7 +2163,7 @@ description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" groups = ["main"] -markers = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"win32\" or python_version >= \"3.12\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"win32\"" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, @@ -2216,7 +2176,6 @@ description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -2280,7 +2239,6 @@ description = "readme_renderer is a library for rendering readme descriptions fo optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151"}, {file = "readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1"}, @@ -2301,7 +2259,6 @@ description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, @@ -2321,7 +2278,6 @@ description = "Reflex Hosting CLI" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "reflex_hosting_cli-0.1.35-py3-none-any.whl", hash = "sha256:619687be27e6691cb54f6cf038e98d4d622fcf25a85bc9986f8daf52b48e6744"}, {file = "reflex_hosting_cli-0.1.35.tar.gz", hash = "sha256:9a5d02978b900045464a1a5581f3adc6260daaa09e8acf95fd05024cda926ae7"}, @@ -2344,7 +2300,6 @@ description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -2367,7 +2322,6 @@ description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -2383,7 +2337,6 @@ description = "Validating URI References per RFC 3986" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, @@ -2399,7 +2352,6 @@ description = "Render rich text, tables, progress bars, syntax highlighting, mar optional = false python-versions = ">=3.8.0" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, @@ -2420,7 +2372,6 @@ description = "An extremely fast Python linter and code formatter, written in Ru optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"}, {file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"}, @@ -2449,7 +2400,7 @@ description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.6" groups = ["main"] -markers = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" or python_version >= \"3.12\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\"" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and sys_platform == \"linux\"" files = [ {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, @@ -2466,7 +2417,6 @@ description = "Official Python bindings for Selenium WebDriver" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "selenium-4.28.1-py3-none-any.whl", hash = "sha256:4238847e45e24e4472cfcf3554427512c7aab9443396435b1623ef406fff1cc1"}, {file = "selenium-4.28.1.tar.gz", hash = "sha256:0072d08670d7ec32db901bd0107695a330cecac9f196e3afb3fa8163026e022a"}, @@ -2487,20 +2437,19 @@ description = "Easily download, build, install, upgrade, and uninstall Python pa optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] -core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "shellingham" @@ -2509,7 +2458,6 @@ description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -2522,7 +2470,6 @@ description = "Simple WebSocket server and client for Python" optional = false python-versions = ">=3.6" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c"}, {file = "simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4"}, @@ -2542,7 +2489,6 @@ description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2555,7 +2501,6 @@ description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2568,7 +2513,6 @@ description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" optional = false python-versions = "*" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, @@ -2581,7 +2525,6 @@ description = "Database Abstraction Library" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "SQLAlchemy-2.0.38-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5e1d9e429028ce04f187a9f522818386c8b076723cdbe9345708384f49ebcec6"}, {file = "SQLAlchemy-2.0.38-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b87a90f14c68c925817423b0424381f0e16d80fc9a1a1046ef202ab25b19a444"}, @@ -2678,7 +2621,6 @@ description = "SQLModel, SQL databases in Python, designed for simplicity, compa optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "sqlmodel-0.0.22-py3-none-any.whl", hash = "sha256:a1ed13e28a1f4057cbf4ff6cdb4fc09e85702621d3259ba17b3c230bfb2f941b"}, {file = "sqlmodel-0.0.22.tar.gz", hash = "sha256:7d37c882a30c43464d143e35e9ecaf945d88035e20117bf5ec2834a23cbe505e"}, @@ -2695,7 +2637,6 @@ description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "starlette-0.45.3-py3-none-any.whl", hash = "sha256:dfb6d332576f136ec740296c7e8bb8c8a7125044e7c6da30744718880cdd059d"}, {file = "starlette-0.45.3.tar.gz", hash = "sha256:2cbcba2a75806f8a41c722141486f37c28e30a0921c5f6fe4346cb0dcee1302f"}, @@ -2714,7 +2655,6 @@ description = "Fast, beautiful and extensible administrative interface framework optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "starlette_admin-0.14.1-py3-none-any.whl", hash = "sha256:5b6260d7ed3db455585852d669feb7ed9a8c5f9a1e3d48d21a52912ec37e18f9"}, {file = "starlette_admin-0.14.1.tar.gz", hash = "sha256:45e2baa3b9a8deec7a6e8ca9295123f648bb0d2070abe68f27193c6d5e32cc38"}, @@ -2730,7 +2670,7 @@ cov = ["coverage[toml] (>=7.0.0,<7.4.0)"] dev = ["pre-commit (>=2.20.0,<4.0.0)", "uvicorn (>=0.20.0,<0.31.0)"] doc = ["mkdocs (>=1.4.2,<2.0.0)", "mkdocs-material (>=9.0.0,<10.0.0)", "mkdocs-static-i18n (>=1.2.3,<1.3)", "mkdocstrings[python] (>=0.19.0,<0.26.0)"] i18n = ["babel (>=2.13.0)"] -test = ["aiomysql (>=0.1.1,<0.3.0)", "aiosqlite (>=0.17.0,<0.21.0)", "arrow (>=1.2.3,<1.4.0)", "asyncpg (>=0.27.0,<0.30.0)", "backports-zoneinfo", "black (==24.4.2)", "colour (>=0.1.5,<0.2.0)", "fasteners (==0.19)", "httpx (>=0.23.3,<0.27.0)", "itsdangerous (>=2.2.0,<2.3.0)", "mongoengine (>=0.25.0,<0.29.0)", "mypy (==1.10.0)", "odmantic (>=0.9.0,<0.10.0)", "passlib (>=1.7.4,<1.8.0)", "phonenumbers (>=8.13.3,<8.14.0)", "pillow (>=9.4.0,<9.6.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pydantic[email] (>=1.10.2,<2.8.0)", "pymysql[rsa] (>=1.0.2,<1.2.0)", "pytest (>=7.2.0,<7.5.0)", "pytest-asyncio (>=0.20.2,<0.24.0)", "ruff (==0.4.8)", "sqlalchemy-file (>=0.5.0,<0.7.0)", "sqlalchemy-utils (>=0.40.0,<0.42.0)", "sqlmodel (>=0.0.11,<0.15.0)", "tinydb (>=4.7.0,<4.9.0)"] +test = ["aiomysql (>=0.1.1,<0.3.0)", "aiosqlite (>=0.17.0,<0.21.0)", "arrow (>=1.2.3,<1.4.0)", "asyncpg (>=0.27.0,<0.30.0)", "backports-zoneinfo ; python_version < \"3.9\"", "black (==24.4.2)", "colour (>=0.1.5,<0.2.0)", "fasteners (==0.19)", "httpx (>=0.23.3,<0.27.0)", "itsdangerous (>=2.2.0,<2.3.0)", "mongoengine (>=0.25.0,<0.29.0)", "mypy (==1.10.0)", "odmantic (>=0.9.0,<0.10.0)", "passlib (>=1.7.4,<1.8.0)", "phonenumbers (>=8.13.3,<8.14.0)", "pillow (>=9.4.0,<9.6.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pydantic[email] (>=1.10.2,<2.8.0)", "pymysql[rsa] (>=1.0.2,<1.2.0)", "pytest (>=7.2.0,<7.5.0)", "pytest-asyncio (>=0.20.2,<0.24.0)", "ruff (==0.4.8)", "sqlalchemy-file (>=0.5.0,<0.7.0)", "sqlalchemy-utils (>=0.40.0,<0.42.0)", "sqlmodel (>=0.0.11,<0.15.0)", "tinydb (>=4.7.0,<4.9.0)"] [[package]] name = "tabulate" @@ -2739,7 +2679,6 @@ description = "Pretty-print tabular data" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, @@ -2755,7 +2694,6 @@ description = "Retry code until it succeeds" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, @@ -2772,7 +2710,6 @@ description = "The most basic Text::Unidecode port" optional = false python-versions = "*" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, @@ -2785,7 +2722,6 @@ description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -2798,6 +2734,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -2832,7 +2769,6 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] -markers = {main = "python_version < \"3.11\"", dev = "python_full_version <= \"3.11.0a6\""} [[package]] name = "tomlkit" @@ -2841,7 +2777,6 @@ description = "Style preserving TOML library" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, @@ -2854,7 +2789,6 @@ description = "A friendly Python library for async concurrency and I/O" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "trio-0.29.0-py3-none-any.whl", hash = "sha256:d8c463f1a9cc776ff63e331aba44c125f423a5a13c684307e828d930e625ba66"}, {file = "trio-0.29.0.tar.gz", hash = "sha256:ea0d3967159fc130acb6939a0be0e558e364fee26b5deeecc893a6b08c361bdf"}, @@ -2876,7 +2810,6 @@ description = "WebSocket library for Trio" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "trio_websocket-0.12.1-py3-none-any.whl", hash = "sha256:608ec746bb287e5d5a66baf483e41194193c5cf05ffaad6240e7d1fcd80d1e6f"}, {file = "trio_websocket-0.12.1.tar.gz", hash = "sha256:d55ccd4d3eae27c494f3fdae14823317839bdcb8214d1173eacc4d42c69fc91b"}, @@ -2895,7 +2828,6 @@ description = "Collection of utilities for publishing packages on PyPI" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "twine-6.1.0-py3-none-any.whl", hash = "sha256:a47f973caf122930bf0fbbf17f80b83bc1602c9ce393c7845f289a3001dc5384"}, {file = "twine-6.1.0.tar.gz", hash = "sha256:be324f6272eff91d07ee93f251edf232fc647935dd585ac003539b42404a8dbd"}, @@ -2922,7 +2854,6 @@ description = "Typer, build great CLIs. Easy to code. Based on Python type hints optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847"}, {file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a"}, @@ -2941,7 +2872,6 @@ description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -2954,7 +2884,6 @@ description = "Provider of IANA time zone data" optional = false python-versions = ">=2" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, @@ -2967,7 +2896,6 @@ description = "HTTP library with thread-safe connection pooling, file post, and optional = false python-versions = ">=3.9" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, @@ -2977,7 +2905,7 @@ files = [ pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2989,7 +2917,6 @@ description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, @@ -3001,7 +2928,7 @@ h11 = ">=0.8" typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "virtualenv" @@ -3010,7 +2937,6 @@ description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a"}, {file = "virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728"}, @@ -3023,7 +2949,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "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) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "websocket-client" @@ -3032,7 +2958,6 @@ description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, @@ -3050,7 +2975,6 @@ description = "A built-package format for Python" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, @@ -3066,7 +2990,6 @@ description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, @@ -3156,7 +3079,6 @@ description = "WebSockets state-machine based protocol implementation" optional = false python-versions = ">=3.7.0" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, @@ -3172,21 +3094,21 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\") or python_full_version < \"3.10.2\"" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version <= \"3.11\" or python_full_version < \"3.10.2\"" files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.10, <4.0" -content-hash = "36de501672441a558232190e75c187dd92682b56a99fcd84dc2dc6ab78f7dfc8" +content-hash = "e28e1cdafe4c96b3804c1801968e634b4c7be81a8d3788587f8585a5c8b20d9b" diff --git a/pyproject.toml b/pyproject.toml index a1871801164..14891183561 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ twine = ">=4.0.0,<7.0" tomlkit = ">=0.12.4,<1.0" lazy_loader = ">=0.4" typing_extensions = ">=4.6.0" +jsonpatch = "^1.33" [tool.poetry.group.dev.dependencies] pytest = ">=7.1.2,<9.0" diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 2f09ac2debd..8d8e6129162 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -16,6 +16,7 @@ import { } from "$/utils/context.js"; import debounce from "$/utils/helpers/debounce"; import throttle from "$/utils/helpers/throttle"; +import { applyPatch } from "fast-json-patch/index.mjs"; // Endpoint URLs. const EVENTURL = env.EVENT; @@ -227,8 +228,8 @@ export const applyEvent = async (event, socket) => { a.href = eval?.( event.payload.url.replace( "getBackendURL(env.UPLOAD)", - `"${getBackendURL(env.UPLOAD)}"`, - ), + `"${getBackendURL(env.UPLOAD)}"` + ) ); } a.download = event.payload.filename; @@ -341,7 +342,7 @@ export const applyRestEvent = async (event, socket) => { event.payload.files, event.payload.upload_id, event.payload.on_upload_progress, - socket, + socket ); return false; } @@ -408,7 +409,7 @@ export const connect = async ( dispatch, transports, setConnectErrors, - client_storage = {}, + client_storage = {} ) => { // Get backend URL object from the endpoint. const endpoint = getBackendURL(EVENTURL); @@ -464,10 +465,20 @@ export const connect = async ( window.removeEventListener("pagehide", pagehideHandler); }); + const last_substate_info = {}; + // On each received message, queue the updates and events. socket.current.on("event", async (update) => { for (const substate in update.delta) { - dispatch[substate](update.delta[substate]); + console.log(last_substate_info[substate]); + const new_substate_info = update.delta[substate].__patch + ? applyPatch( + last_substate_info[substate], + update.delta[substate].__patch + ).newDocument + : update.delta[substate]; + last_substate_info[substate] = new_substate_info; + dispatch[substate](new_substate_info); } applyClientStorageDelta(client_storage, update.delta); event_processing = !update.final; @@ -499,7 +510,7 @@ export const uploadFiles = async ( files, upload_id, on_upload_progress, - socket, + socket ) => { // return if there's no file to upload if (files === undefined || files.length === 0) { @@ -604,7 +615,7 @@ export const Event = ( name, payload = {}, event_actions = {}, - handler = null, + handler = null ) => { return { name, payload, handler, event_actions }; }; @@ -631,7 +642,7 @@ export const hydrateClientStorage = (client_storage) => { for (const state_key in client_storage.local_storage) { const options = client_storage.local_storage[state_key]; const local_storage_value = localStorage.getItem( - options.name || state_key, + options.name || state_key ); if (local_storage_value !== null) { client_storage_values[state_key] = local_storage_value; @@ -642,7 +653,7 @@ export const hydrateClientStorage = (client_storage) => { for (const state_key in client_storage.session_storage) { const session_options = client_storage.session_storage[state_key]; const session_storage_value = sessionStorage.getItem( - session_options.name || state_key, + session_options.name || state_key ); if (session_storage_value != null) { client_storage_values[state_key] = session_storage_value; @@ -667,7 +678,7 @@ export const hydrateClientStorage = (client_storage) => { const applyClientStorageDelta = (client_storage, delta) => { // find the main state and check for is_hydrated const unqualified_states = Object.keys(delta).filter( - (key) => key.split(".").length === 1, + (key) => key.split(".").length === 1 ); if (unqualified_states.length === 1) { const main_state = delta[unqualified_states[0]]; @@ -701,7 +712,7 @@ const applyClientStorageDelta = (client_storage, delta) => { const session_options = client_storage.session_storage[state_key]; sessionStorage.setItem( session_options.name || state_key, - delta[substate][key], + delta[substate][key] ); } } @@ -721,7 +732,7 @@ const applyClientStorageDelta = (client_storage, delta) => { export const useEventLoop = ( dispatch, initial_events = () => [], - client_storage = {}, + client_storage = {} ) => { const socket = useRef(null); const router = useRouter(); @@ -735,7 +746,7 @@ export const useEventLoop = ( event_actions = events.reduce( (acc, e) => ({ ...acc, ...e.event_actions }), - event_actions ?? {}, + event_actions ?? {} ); const _e = args.filter((o) => o?.preventDefault !== undefined)[0]; @@ -763,7 +774,7 @@ export const useEventLoop = ( debounce( combined_name, () => queueEvents(events, socket), - event_actions.debounce, + event_actions.debounce ); } else { queueEvents(events, socket); @@ -782,7 +793,7 @@ export const useEventLoop = ( query, asPath, }))(router), - })), + })) ); sentHydrate.current = true; } @@ -828,7 +839,7 @@ export const useEventLoop = ( dispatch, ["websocket"], setConnectErrors, - client_storage, + client_storage ); } } @@ -876,7 +887,7 @@ export const useEventLoop = ( vars[storage_to_state_map[e.key]] = e.newValue; const event = Event( `${state_name}.reflex___state____update_vars_internal_state.update_vars_internal`, - { vars: vars }, + { vars: vars } ); addEvents([event], e); } @@ -969,7 +980,7 @@ export const getRefValues = (refs) => { return refs.map((ref) => ref.current ? ref.current.value || ref.current.getAttribute("aria-valuenow") - : null, + : null ); }; diff --git a/reflex/app.py b/reflex/app.py index c9e6c2df49f..8197dceab18 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -1407,7 +1407,7 @@ async def modify_state(self, token: str) -> AsyncIterator[BaseState]: async with self.state_manager.modify_state(token) as state: # No other event handler can modify the state while in this context. yield state - delta = state.get_delta() + delta = state.get_delta(token=token) if delta: # When the state is modified reset dirty status and emit the delta to the frontend. state._clean() diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index 5e9947a937e..ce3d0c2e0b3 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -27,7 +27,7 @@ ) from reflex.components.component import Component, ComponentStyle, CustomComponent from reflex.istate.storage import Cookie, LocalStorage, SessionStorage -from reflex.state import BaseState, _resolve_delta +from reflex.state import BaseState, StateDelta, _resolve_delta from reflex.style import Style from reflex.utils import console, format, imports, path_ops from reflex.utils.exec import is_in_app_harness @@ -187,7 +187,7 @@ def compile_state(state: Type[BaseState]) -> dict: Returns: A dictionary of the compiled state. """ - initial_state = state(_reflex_internal_init=True).dict(initial=True) + initial_state = StateDelta(state(_reflex_internal_init=True).dict(initial=True)) try: _ = asyncio.get_running_loop() except RuntimeError: @@ -202,10 +202,10 @@ def compile_state(state: Type[BaseState]) -> dict: console.warn( f"Had to get initial state in a thread 🤮 {resolved_initial_state}", ) - return resolved_initial_state + return resolved_initial_state.data # Normally the compile runs before any event loop starts, we asyncio.run is available for calling. - return asyncio.run(_resolve_delta(initial_state)) + return asyncio.run(_resolve_delta(initial_state)).data def _compile_client_storage_field( diff --git a/reflex/config.py b/reflex/config.py index 066c3f8e66a..87859c424d6 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -718,6 +718,9 @@ class EnvironmentVariables: # Used by flexgen to enumerate the pages. REFLEX_ADD_ALL_ROUTES_ENDPOINT: EnvVar[bool] = env_var(False) + # Use the JSON patch format for websocket messages. + REFLEX_USE_JSON_PATCH: EnvVar[bool] = env_var(False) + environment = EnvironmentVariables() diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index baad18f485f..16a86b84724 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -188,6 +188,7 @@ class Commands(SimpleNamespace): "react-dom": "19.0.0", "react-focus-lock": "2.13.6", "socket.io-client": "4.8.1", + "fast-json-patch": "3.1.1", "universal-cookie": "7.2.2", } DEV_DEPENDENCIES = { diff --git a/reflex/middleware/hydrate_middleware.py b/reflex/middleware/hydrate_middleware.py index ec18939dee3..70f267ab291 100644 --- a/reflex/middleware/hydrate_middleware.py +++ b/reflex/middleware/hydrate_middleware.py @@ -8,7 +8,7 @@ from reflex import constants from reflex.event import Event, get_hydrate_event from reflex.middleware.middleware import Middleware -from reflex.state import BaseState, StateUpdate, _resolve_delta +from reflex.state import BaseState, StateDelta, StateUpdate, _resolve_delta if TYPE_CHECKING: from reflex.app import App @@ -42,7 +42,13 @@ async def preprocess( setattr(state, constants.CompileVars.IS_HYDRATED, False) # Get the initial state. - delta = await _resolve_delta(state.dict()) + delta = await _resolve_delta( + StateDelta( + state.dict(), + reflex_delta_token=state.router.session.client_token, + flush=True, + ) + ) # since a full dict was captured, clean any dirtiness state._clean() diff --git a/reflex/state.py b/reflex/state.py index d414efc8a42..3d83920a700 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -40,6 +40,7 @@ import pydantic.v1 as pydantic import wrapt +from jsonpatch import make_patch from pydantic import BaseModel as BaseModelV2 from pydantic.v1 import BaseModel as BaseModelV1 from pydantic.v1 import validator @@ -108,10 +109,135 @@ from reflex.components.component import Component -Delta = dict[str, Any] var = computed_var +@dataclasses.dataclass +class StateDelta: + """A dictionary representing the state delta.""" + + data: dict[str, Any] = dataclasses.field(default_factory=dict) + reflex_delta_token: str | None = dataclasses.field(default=None) + flush: bool = dataclasses.field(default=False) + + def __getitem__(self, key: str) -> Any: + """Get the item from the delta. + + Args: + key: The key to get. + + Returns: + The item from the delta. + """ + return self.data[key] + + def __setitem__(self, key: str, value: Any): + """Set the item in the delta. + + Args: + key: The key to set. + value: The value to set. + """ + self.data[key] = value + + def __delitem__(self, key: str): + """Delete the item from the delta. + + Args: + key: The key to delete. + """ + del self.data[key] + + def __iter__(self) -> Any: + """Iterate over the delta. + + Returns: + The iterator over the delta. + """ + return iter(self.data) + + def __len__(self) -> int: + """Get the length of the delta. + + Returns: + The length of the delta. + """ + return len(self.data) + + def __contains__(self, key: str) -> bool: + """Check if the delta contains the key. + + Args: + key: The key to check. + + Returns: + Whether the delta contains the key. + """ + return key in self.data + + def keys(self): + """Get the keys of the delta. + + Returns: + The keys of the delta. + """ + return self.data.keys() + + def __reversed__(self): + """Reverse the delta. + + Returns: + The reversed delta. + """ + return reversed(self.data) + + def values(self): + """Get the values of the delta. + + Returns: + The values of the delta. + """ + return self.data.values() + + def items(self): + """Get the items of the delta. + + Returns: + The items of the delta. + """ + return self.data.items() + + +LAST_DELTA_CACHE: dict[str, StateDelta] = {} + + +@serializer(to=dict) +def serialize_state_delta(delta: StateDelta) -> dict[str, Any]: + """Serialize the state delta. + + Args: + delta: The state delta to serialize. + + Returns: + The serialized state delta. + """ + if delta.reflex_delta_token is not None and environment.REFLEX_USE_JSON_PATCH.get(): + full_delta = {} + for state_name, new_state_value in delta.items(): + new_state_value = json.loads(format.json_dumps(new_state_value)) + key = delta.reflex_delta_token + state_name + previous_delta = LAST_DELTA_CACHE.get(key) + LAST_DELTA_CACHE[key] = new_state_value + if previous_delta is not None and not delta.flush: + full_delta[state_name] = { + "__patch": make_patch(previous_delta, new_state_value).patch + } + else: + full_delta[state_name] = new_state_value + return full_delta + return delta.data + + if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF: # If the state is this large, it's considered a performance issue. TOO_LARGE_SERIALIZED_STATE = environment.REFLEX_STATE_SIZE_LIMIT.get() * 1024 @@ -306,7 +432,7 @@ def get_var_for_field(cls: Type[BaseState], f: ModelField): ) -async def _resolve_delta(delta: Delta) -> Delta: +async def _resolve_delta(delta: StateDelta) -> StateDelta: """Await all coroutines in the delta. Args: @@ -1679,7 +1805,7 @@ async def _as_state_update( try: # Get the delta after processing the event. - delta = await _resolve_delta(state.get_delta()) + delta = await _resolve_delta(state.get_delta(token=token)) state._clean() return StateUpdate( @@ -1888,9 +2014,12 @@ def _dirty_computed_vars( if include_backend or not self.computed_vars[cvar]._backend } - def get_delta(self) -> Delta: + def get_delta(self, *, token: str | None = None) -> StateDelta: """Get the delta for the state. + Args: + token: The reflex delta + Returns: The delta for the state. """ @@ -1922,7 +2051,7 @@ def get_delta(self) -> Delta: delta.update(substates[substate].get_delta()) # Return the delta. - return delta + return StateDelta(delta, reflex_delta_token=token) def _mark_dirty(self): """Mark the substate and all parent states as dirty.""" @@ -2753,7 +2882,7 @@ class StateUpdate: """A state update sent to the frontend.""" # The state delta. - delta: Delta = dataclasses.field(default_factory=dict) + delta: StateDelta = dataclasses.field(default_factory=StateDelta) # Events to be added to the event queue. events: list[Event] = dataclasses.field(default_factory=list) diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 9777ba9d3c3..48a1d829b27 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -41,6 +41,7 @@ OnLoadInternalState, RouterData, State, + StateDelta, StateManagerDisk, StateManagerMemory, StateManagerRedis, @@ -1050,14 +1051,16 @@ def _dynamic_state_event(name, val, **kwargs): update = await process_coro.__anext__() # route change (on_load_internal) triggers: [call on_load events, call set_is_hydrated(True)] assert update == StateUpdate( - delta={ - state.get_name(): { - arg_name: exp_val, - f"comp_{arg_name}": exp_val, - constants.CompileVars.IS_HYDRATED: False, - "router": exp_router, + delta=StateDelta( + { + state.get_name(): { + arg_name: exp_val, + f"comp_{arg_name}": exp_val, + constants.CompileVars.IS_HYDRATED: False, + "router": exp_router, + } } - }, + ), events=[ _dynamic_state_event( name="on_load", @@ -1093,11 +1096,13 @@ def _dynamic_state_event(name, val, **kwargs): ) on_load_update = await process_coro.__anext__() assert on_load_update == StateUpdate( - delta={ - state.get_name(): { - "loaded": exp_index + 1, - }, - }, + delta=StateDelta( + { + state.get_name(): { + "loaded": exp_index + 1, + }, + } + ), events=[], ) # complete the processing @@ -1114,11 +1119,13 @@ def _dynamic_state_event(name, val, **kwargs): ) on_set_is_hydrated_update = await process_coro.__anext__() assert on_set_is_hydrated_update == StateUpdate( - delta={ - state.get_name(): { - "is_hydrated": True, - }, - }, + delta=StateDelta( + { + state.get_name(): { + "is_hydrated": True, + }, + } + ), events=[], ) # complete the processing @@ -1135,11 +1142,13 @@ def _dynamic_state_event(name, val, **kwargs): ) update = await process_coro.__anext__() assert update == StateUpdate( - delta={ - state.get_name(): { - "counter": exp_index + 1, + delta=StateDelta( + { + state.get_name(): { + "counter": exp_index + 1, + } } - }, + ), events=[], ) # complete the processing diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 0619b4560ba..70163ea66e0 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -34,6 +34,7 @@ OnLoadInternalState, RouterData, State, + StateDelta, StateManager, StateManagerDisk, StateManagerMemory, @@ -2012,14 +2013,16 @@ async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App): mcall = mock_app.event_namespace.emit.mock_calls[0] # pyright: ignore [reportFunctionMemberAccess] assert mcall.args[0] == str(SocketEvent.EVENT) assert mcall.args[1] == StateUpdate( - delta={ - grandchild_state.get_full_name(): { - "value2": "42", - }, - GrandchildState3.get_full_name(): { - "computed": "", - }, - } + delta=StateDelta( + { + grandchild_state.get_full_name(): { + "value2": "42", + }, + GrandchildState3.get_full_name(): { + "computed": "", + }, + } + ) ) assert mcall.kwargs["to"] == grandchild_state.router.session.session_id @@ -2174,18 +2177,20 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): ): # other task returns delta assert update == StateUpdate( - delta={ - BackgroundTaskState.get_full_name(): { - "order": [ - "background_task:start", - "other", - ], - "computed_order": [ - "background_task:start", - "other", - ], + delta=StateDelta( + { + BackgroundTaskState.get_full_name(): { + "order": [ + "background_task:start", + "other", + ], + "computed_order": [ + "background_task:start", + "other", + ], + } } - } + ) ) # Explicit wait for background tasks @@ -2216,42 +2221,50 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): is not None ) assert first_ws_message == StateUpdate( - delta={ - BackgroundTaskState.get_full_name(): { - "order": ["background_task:start"], - "computed_order": ["background_task:start"], + delta=StateDelta( + { + BackgroundTaskState.get_full_name(): { + "order": ["background_task:start"], + "computed_order": ["background_task:start"], + } } - }, + ), events=[], final=True, ) for call in emit_mock.mock_calls[1:5]: # pyright: ignore [reportFunctionMemberAccess] assert call.args[1] == StateUpdate( - delta={ - BackgroundTaskState.get_full_name(): { - "computed_order": ["background_task:start"], + delta=StateDelta( + { + BackgroundTaskState.get_full_name(): { + "computed_order": ["background_task:start"], + } } - }, + ), events=[], final=True, ) assert emit_mock.mock_calls[-2].args[1] == StateUpdate( # pyright: ignore [reportFunctionMemberAccess] - delta={ - BackgroundTaskState.get_full_name(): { - "order": exp_order, - "computed_order": exp_order, - "dict_list": {}, + delta=StateDelta( + { + BackgroundTaskState.get_full_name(): { + "order": exp_order, + "computed_order": exp_order, + "dict_list": {}, + } } - }, + ), events=[], final=True, ) assert emit_mock.mock_calls[-1].args[1] == StateUpdate( # pyright: ignore [reportFunctionMemberAccess] - delta={ - BackgroundTaskState.get_full_name(): { - "computed_order": exp_order, - }, - }, + delta=StateDelta( + { + BackgroundTaskState.get_full_name(): { + "computed_order": exp_order, + }, + } + ), events=[], final=True, ) From ec57a3c0c88d798ff3d2af617451a1edf136c398 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 28 Feb 2025 19:04:33 -0800 Subject: [PATCH 02/14] fix tests --- .../middleware/test_hydrate_middleware.py | 2 +- tests/units/test_app.py | 18 +++-- tests/units/test_state.py | 68 ++++++++++--------- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/tests/units/middleware/test_hydrate_middleware.py b/tests/units/middleware/test_hydrate_middleware.py index 7b02f8515cf..fcd0bd6809c 100644 --- a/tests/units/middleware/test_hydrate_middleware.py +++ b/tests/units/middleware/test_hydrate_middleware.py @@ -46,5 +46,5 @@ async def test_preprocess_no_events(hydrate_middleware, event1, mocker): state=state, ) assert isinstance(update, StateUpdate) - assert update.delta == state.dict() + assert update.delta.data == state.dict() assert not update.events diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 48a1d829b27..2b8d26bca0b 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -480,7 +480,7 @@ async def test_dynamic_var_event(test_state: Type[ATestState], token: str): payload={"value": 50}, ) ): - assert result.delta == {test_state.get_name(): {"int_val": 50}} + assert result.delta.data == {test_state.get_name(): {"int_val": 50}} @pytest.mark.asyncio @@ -594,7 +594,7 @@ async def test_list_mutation_detection__plain_list( ): # prefix keys in expected_delta with the state name expected_delta = {list_mutation_state.get_name(): expected_delta} - assert result.delta == expected_delta + assert result.delta.data == expected_delta @pytest.mark.asyncio @@ -720,7 +720,7 @@ async def test_dict_mutation_detection__plain_list( # prefix keys in expected_delta with the state name expected_delta = {dict_mutation_state.get_name(): expected_delta} - assert result.delta == expected_delta + assert result.delta.data == expected_delta @pytest.mark.asyncio @@ -1059,7 +1059,8 @@ def _dynamic_state_event(name, val, **kwargs): constants.CompileVars.IS_HYDRATED: False, "router": exp_router, } - } + }, + reflex_delta_token=token, ), events=[ _dynamic_state_event( @@ -1101,7 +1102,8 @@ def _dynamic_state_event(name, val, **kwargs): state.get_name(): { "loaded": exp_index + 1, }, - } + }, + reflex_delta_token=token, ), events=[], ) @@ -1124,7 +1126,8 @@ def _dynamic_state_event(name, val, **kwargs): state.get_name(): { "is_hydrated": True, }, - } + }, + reflex_delta_token=token, ), events=[], ) @@ -1147,7 +1150,8 @@ def _dynamic_state_event(name, val, **kwargs): state.get_name(): { "counter": exp_index + 1, } - } + }, + reflex_delta_token=token, ), events=[], ) diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 70163ea66e0..e9938156036 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -784,7 +784,7 @@ async def test_process_event_simple(test_state): assert test_state.num1 == 69 # The delta should contain the changes, including computed vars. - assert update.delta == { + assert update.delta.data == { TestState.get_full_name(): {"num1": 69, "sum": 72.14}, GrandchildState3.get_full_name(): {"computed": ""}, } @@ -811,7 +811,7 @@ async def test_process_event_substate(test_state, child_state, grandchild_state) async for update in test_state._process(event): assert child_state.value == "HI" assert child_state.count == 24 - assert update.delta == { + assert update.delta.data == { # TestState.get_full_name(): {"sum": 3.14, "upper": ""}, ChildState.get_full_name(): {"value": "HI", "count": 24}, GrandchildState3.get_full_name(): {"computed": ""}, @@ -827,7 +827,7 @@ async def test_process_event_substate(test_state, child_state, grandchild_state) ) async for update in test_state._process(event): assert grandchild_state.value2 == "new" - assert update.delta == { + assert update.delta.data == { # TestState.get_full_name(): {"sum": 3.14, "upper": ""}, GrandchildState.get_full_name(): {"value2": "new"}, GrandchildState3.get_full_name(): {"computed": ""}, @@ -849,11 +849,11 @@ async def test_process_event_generator(): async for update in gen: count += 1 if count == 6: - assert update.delta == {} + assert update.delta.data == {} assert update.final else: assert gen_state.value == count - assert update.delta == { + assert update.delta.data == { GenState.get_full_name(): {"value": count}, } assert not update.final @@ -1069,7 +1069,7 @@ def test_not_dirty_computed_var_from_var( interdependent_state: A state with varying Var dependencies. """ interdependent_state.x = 5 - assert interdependent_state.get_delta() == { + assert interdependent_state.get_delta().data == { interdependent_state.get_full_name(): {"x": 5}, } @@ -1084,7 +1084,7 @@ def test_dirty_computed_var_from_var(interdependent_state: InterdependentState) interdependent_state: A state with varying Var dependencies. """ interdependent_state.v1 = 1 - assert interdependent_state.get_delta() == { + assert interdependent_state.get_delta().data == { interdependent_state.get_full_name(): {"v1": 1, "v1x2": 2, "v1x2x2": 4}, } @@ -1100,7 +1100,7 @@ def test_dirty_computed_var_from_backend_var( # Accessing ._v3 returns the immutable var it represents instead of the actual computed var # assert InterdependentState._v3._backend is True interdependent_state._v2 = 2 - assert interdependent_state.get_delta() == { + assert interdependent_state.get_delta().data == { interdependent_state.get_full_name(): {"v2x2": 4, "v3x2": 4}, } @@ -1271,23 +1271,23 @@ def comp_v(self) -> int: cs = ComputedState() assert cs.dirty_vars == set() - assert cs.get_delta() == {cs.get_name(): {"no_cache_v": 0, "dep_v": 0}} + assert cs.get_delta().data == {cs.get_name(): {"no_cache_v": 0, "dep_v": 0}} cs._clean() assert cs.dirty_vars == set() - assert cs.get_delta() == {cs.get_name(): {"no_cache_v": 0, "dep_v": 0}} + assert cs.get_delta().data == {cs.get_name(): {"no_cache_v": 0, "dep_v": 0}} cs._clean() assert cs.dirty_vars == set() cs.v = 1 assert cs.dirty_vars == {"v", "comp_v", "dep_v", "no_cache_v"} - assert cs.get_delta() == { + assert cs.get_delta().data == { cs.get_name(): {"v": 1, "no_cache_v": 1, "dep_v": 1, "comp_v": 1} } cs._clean() assert cs.dirty_vars == set() - assert cs.get_delta() == {cs.get_name(): {"no_cache_v": 1, "dep_v": 1}} + assert cs.get_delta().data == {cs.get_name(): {"no_cache_v": 1, "dep_v": 1}} cs._clean() assert cs.dirty_vars == set() - assert cs.get_delta() == {cs.get_name(): {"no_cache_v": 1, "dep_v": 1}} + assert cs.get_delta().data == {cs.get_name(): {"no_cache_v": 1, "dep_v": 1}} cs._clean() assert cs.dirty_vars == set() @@ -2021,7 +2021,8 @@ async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App): GrandchildState3.get_full_name(): { "computed": "", }, - } + }, + reflex_delta_token="_" + grandchild_state.get_full_name(), ) ) assert mcall.kwargs["to"] == grandchild_state.router.session.session_id @@ -2189,7 +2190,8 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): "other", ], } - } + }, + reflex_delta_token=token, ) ) @@ -2227,7 +2229,8 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): "order": ["background_task:start"], "computed_order": ["background_task:start"], } - } + }, + reflex_delta_token=token + "_" + BackgroundTaskState.get_full_name(), ), events=[], final=True, @@ -2239,7 +2242,8 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): BackgroundTaskState.get_full_name(): { "computed_order": ["background_task:start"], } - } + }, + reflex_delta_token=token + "_" + BackgroundTaskState.get_full_name(), ), events=[], final=True, @@ -2252,7 +2256,8 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): "computed_order": exp_order, "dict_list": {}, } - } + }, + reflex_delta_token=token + "_" + BackgroundTaskState.get_full_name(), ), events=[], final=True, @@ -2263,7 +2268,8 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): BackgroundTaskState.get_full_name(): { "computed_order": exp_order, }, - } + }, + reflex_delta_token=token, ), events=[], final=True, @@ -2904,14 +2910,14 @@ async def test_preprocess(app_module_mock, token, test_state, expected, mocker): updates.append(update) assert len(updates) == 1 assert updates[0].delta[State.get_name()].pop("router") is not None - assert updates[0].delta == exp_is_hydrated(state, False) + assert updates[0].delta.data == exp_is_hydrated(state, False) events = updates[0].events assert len(events) == 2 async for update in state._process(events[0]): - assert update.delta == {test_state.get_full_name(): {"num": 1}} + assert update.delta.data == {test_state.get_full_name(): {"num": 1}} async for update in state._process(events[1]): - assert update.delta == exp_is_hydrated(state) + assert update.delta.data == exp_is_hydrated(state) if isinstance(app.state_manager, StateManagerRedis): await app.state_manager.close() @@ -2952,16 +2958,16 @@ async def test_preprocess_multiple_load_events(app_module_mock, token, mocker): updates.append(update) assert len(updates) == 1 assert updates[0].delta[State.get_name()].pop("router") is not None - assert updates[0].delta == exp_is_hydrated(state, False) + assert updates[0].delta.data == exp_is_hydrated(state, False) events = updates[0].events assert len(events) == 3 async for update in state._process(events[0]): - assert update.delta == {OnLoadState.get_full_name(): {"num": 1}} + assert update.delta.data == {OnLoadState.get_full_name(): {"num": 1}} async for update in state._process(events[1]): - assert update.delta == {OnLoadState.get_full_name(): {"num": 2}} + assert update.delta.data == {OnLoadState.get_full_name(): {"num": 2}} async for update in state._process(events[2]): - assert update.delta == exp_is_hydrated(state) + assert update.delta.data == exp_is_hydrated(state) if isinstance(app.state_manager, StateManagerRedis): await app.state_manager.close() @@ -3033,7 +3039,7 @@ async def test_get_state(mock_app: rx.App, token: str): ) grandchild_state.value2 = "set_value" - assert test_state.get_delta() == { + assert test_state.get_delta().data == { GrandchildState.get_full_name(): { "value2": "set_value", }, @@ -3070,7 +3076,7 @@ async def test_get_state(mock_app: rx.App, token: str): child_state2 = new_test_state.get_substate((ChildState2.get_name(),)) child_state2.value = "set_c2_value" - assert new_test_state.get_delta() == { + assert new_test_state.get_delta().data == { ChildState2.get_full_name(): { "value": "set_c2_value", }, @@ -3641,7 +3647,7 @@ class GetValueState(rx.State): "bar": "BAR", } } - assert state.get_delta() == {} + assert state.get_delta().data == {} state.bar = "foo" @@ -3651,7 +3657,7 @@ class GetValueState(rx.State): "bar": "foo", } } - assert state.get_delta() == { + assert state.get_delta().data == { state.get_full_name(): { "bar": "foo", } @@ -3774,7 +3780,7 @@ async def test_upcast_event_handler_arg(handler, payload): """ state = UpcastState() async for update in state._process_event(handler, state, payload): - assert update.delta == {UpcastState.get_full_name(): {"passed": True}} + assert update.delta.data == {UpcastState.get_full_name(): {"passed": True}} @pytest.mark.asyncio From 27493b85aa3254ab1b48d3f633626e3d32294b18 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Sat, 1 Mar 2025 18:31:11 -0800 Subject: [PATCH 03/14] fix redis test --- tests/units/test_state_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/units/test_state_tree.py b/tests/units/test_state_tree.py index 70ef71cb864..b6069beca1b 100644 --- a/tests/units/test_state_tree.py +++ b/tests/units/test_state_tree.py @@ -374,7 +374,7 @@ async def test_get_state_tree( assert sorted(state.substates) == sorted(exp_root_substates) # Only computed vars should be returned - assert state.get_delta() == ALWAYS_COMPUTED_VARS + assert state.get_delta().data == ALWAYS_COMPUTED_VARS # All of TreeA, TreeD, and TreeE substates should be in the dict assert sorted(state.dict()) == sorted(exp_root_dict_keys) From 3210204a4539c686d330c70229480283d836e220 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 3 Mar 2025 11:28:05 -0800 Subject: [PATCH 04/14] rename reflex_delta_token to client_token --- reflex/middleware/hydrate_middleware.py | 2 +- reflex/state.py | 10 +++++----- tests/units/test_app.py | 8 ++++---- tests/units/test_state.py | 12 ++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/reflex/middleware/hydrate_middleware.py b/reflex/middleware/hydrate_middleware.py index 70f267ab291..e88c6ca1ac5 100644 --- a/reflex/middleware/hydrate_middleware.py +++ b/reflex/middleware/hydrate_middleware.py @@ -45,7 +45,7 @@ async def preprocess( delta = await _resolve_delta( StateDelta( state.dict(), - reflex_delta_token=state.router.session.client_token, + client_token=state.router.session.client_token, flush=True, ) ) diff --git a/reflex/state.py b/reflex/state.py index 3d83920a700..21eaeb15538 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -117,7 +117,7 @@ class StateDelta: """A dictionary representing the state delta.""" data: dict[str, Any] = dataclasses.field(default_factory=dict) - reflex_delta_token: str | None = dataclasses.field(default=None) + client_token: str | None = dataclasses.field(default=None) flush: bool = dataclasses.field(default=False) def __getitem__(self, key: str) -> Any: @@ -221,11 +221,11 @@ def serialize_state_delta(delta: StateDelta) -> dict[str, Any]: Returns: The serialized state delta. """ - if delta.reflex_delta_token is not None and environment.REFLEX_USE_JSON_PATCH.get(): + if delta.client_token is not None and environment.REFLEX_USE_JSON_PATCH.get(): full_delta = {} for state_name, new_state_value in delta.items(): new_state_value = json.loads(format.json_dumps(new_state_value)) - key = delta.reflex_delta_token + state_name + key = delta.client_token + state_name previous_delta = LAST_DELTA_CACHE.get(key) LAST_DELTA_CACHE[key] = new_state_value if previous_delta is not None and not delta.flush: @@ -2018,7 +2018,7 @@ def get_delta(self, *, token: str | None = None) -> StateDelta: """Get the delta for the state. Args: - token: The reflex delta + token: The client token. Returns: The delta for the state. @@ -2051,7 +2051,7 @@ def get_delta(self, *, token: str | None = None) -> StateDelta: delta.update(substates[substate].get_delta()) # Return the delta. - return StateDelta(delta, reflex_delta_token=token) + return StateDelta(delta, client_token=token) def _mark_dirty(self): """Mark the substate and all parent states as dirty.""" diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 2b8d26bca0b..1455b8f4bf8 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -1060,7 +1060,7 @@ def _dynamic_state_event(name, val, **kwargs): "router": exp_router, } }, - reflex_delta_token=token, + client_token=token, ), events=[ _dynamic_state_event( @@ -1103,7 +1103,7 @@ def _dynamic_state_event(name, val, **kwargs): "loaded": exp_index + 1, }, }, - reflex_delta_token=token, + client_token=token, ), events=[], ) @@ -1127,7 +1127,7 @@ def _dynamic_state_event(name, val, **kwargs): "is_hydrated": True, }, }, - reflex_delta_token=token, + client_token=token, ), events=[], ) @@ -1151,7 +1151,7 @@ def _dynamic_state_event(name, val, **kwargs): "counter": exp_index + 1, } }, - reflex_delta_token=token, + client_token=token, ), events=[], ) diff --git a/tests/units/test_state.py b/tests/units/test_state.py index e9938156036..4db38d6563c 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -2022,7 +2022,7 @@ async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App): "computed": "", }, }, - reflex_delta_token="_" + grandchild_state.get_full_name(), + client_token="_" + grandchild_state.get_full_name(), ) ) assert mcall.kwargs["to"] == grandchild_state.router.session.session_id @@ -2191,7 +2191,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): ], } }, - reflex_delta_token=token, + client_token=token, ) ) @@ -2230,7 +2230,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): "computed_order": ["background_task:start"], } }, - reflex_delta_token=token + "_" + BackgroundTaskState.get_full_name(), + client_token=token + "_" + BackgroundTaskState.get_full_name(), ), events=[], final=True, @@ -2243,7 +2243,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): "computed_order": ["background_task:start"], } }, - reflex_delta_token=token + "_" + BackgroundTaskState.get_full_name(), + client_token=token + "_" + BackgroundTaskState.get_full_name(), ), events=[], final=True, @@ -2257,7 +2257,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): "dict_list": {}, } }, - reflex_delta_token=token + "_" + BackgroundTaskState.get_full_name(), + client_token=token + "_" + BackgroundTaskState.get_full_name(), ), events=[], final=True, @@ -2269,7 +2269,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): "computed_order": exp_order, }, }, - reflex_delta_token=token, + client_token=token, ), events=[], final=True, From e0bf5b2ac9b91ab81ad87d90890afb364b5337b6 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 3 Mar 2025 12:15:47 -0800 Subject: [PATCH 05/14] add __full in symmetry to __patch --- reflex/.templates/web/utils/state.js | 2 +- reflex/state.py | 6 ++++-- tests/units/test_app.py | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 8d8e6129162..98d05313db8 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -476,7 +476,7 @@ export const connect = async ( last_substate_info[substate], update.delta[substate].__patch ).newDocument - : update.delta[substate]; + : update.delta[substate].__full; last_substate_info[substate] = new_substate_info; dispatch[substate](new_substate_info); } diff --git a/reflex/state.py b/reflex/state.py index 21eaeb15538..060bac6fa07 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -233,9 +233,11 @@ def serialize_state_delta(delta: StateDelta) -> dict[str, Any]: "__patch": make_patch(previous_delta, new_state_value).patch } else: - full_delta[state_name] = new_state_value + full_delta[state_name] = {"__full": new_state_value} return full_delta - return delta.data + return { + state_name: {"__full": state_value} for state_name, state_value in delta.items() + } if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF: diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 1455b8f4bf8..e588f601a24 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -731,7 +731,7 @@ async def test_dict_mutation_detection__plain_list( FileUploadState, { FileUploadState.get_full_name(): { - "img_list": ["image1.jpg", "image2.jpg"] + "__full": {"img_list": ["image1.jpg", "image2.jpg"]} } }, ), @@ -739,7 +739,7 @@ async def test_dict_mutation_detection__plain_list( ChildFileUploadState, { ChildFileUploadState.get_full_name(): { - "img_list": ["image1.jpg", "image2.jpg"] + "__full": {"img_list": ["image1.jpg", "image2.jpg"]} } }, ), @@ -747,7 +747,7 @@ async def test_dict_mutation_detection__plain_list( GrandChildFileUploadState, { GrandChildFileUploadState.get_full_name(): { - "img_list": ["image1.jpg", "image2.jpg"] + "__full": {"img_list": ["image1.jpg", "image2.jpg"]} } }, ), From 927dc5302f75389a590b6e10bd39c50928c12285 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 3 Mar 2025 12:40:34 -0800 Subject: [PATCH 06/14] handle hash logic --- reflex/.templates/web/utils/state.js | 30 +++++++++++++++++++++------- reflex/state.py | 29 ++++++++++++++++++++------- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 98d05313db8..72b86a48a51 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -466,17 +466,33 @@ export const connect = async ( }); const last_substate_info = {}; + const last_substate_hash = {}; + + const getSubstateFromUpdate = (update, substate_name) => { + if (update.__patch) { + if (last_substate_hash[substate_name] !== update.__previous_hash) { + throw new Error( + "Patch received out of order" + + update.__hash + + " " + + last_substate_hash[substate_name] + ); + } + last_substate_hash[substate_name] = update.__hash; + return applyPatch(last_substate_info, update.__patch).newDocument; + } else { + last_substate_hash[substate_name] = update.__hash; + return update.__full; + } + }; // On each received message, queue the updates and events. socket.current.on("event", async (update) => { for (const substate in update.delta) { - console.log(last_substate_info[substate]); - const new_substate_info = update.delta[substate].__patch - ? applyPatch( - last_substate_info[substate], - update.delta[substate].__patch - ).newDocument - : update.delta[substate].__full; + const new_substate_info = getSubstateFromUpdate( + update.delta[substate], + substate + ); last_substate_info[substate] = new_substate_info; dispatch[substate](new_substate_info); } diff --git a/reflex/state.py b/reflex/state.py index 060bac6fa07..8bb2ce3bf89 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -26,6 +26,7 @@ Callable, ClassVar, Dict, + NamedTuple, Optional, Sequence, Set, @@ -208,7 +209,14 @@ def items(self): return self.data.items() -LAST_DELTA_CACHE: dict[str, StateDelta] = {} +class DeltaCache(NamedTuple): + """A named tuple representing the delta cache.""" + + hash: int + delta: StateDelta + + +LAST_DELTA_CACHE: dict[str, DeltaCache] = {} @serializer(to=dict) @@ -224,16 +232,23 @@ def serialize_state_delta(delta: StateDelta) -> dict[str, Any]: if delta.client_token is not None and environment.REFLEX_USE_JSON_PATCH.get(): full_delta = {} for state_name, new_state_value in delta.items(): - new_state_value = json.loads(format.json_dumps(new_state_value)) + json_str = format.json_dumps(new_state_value) + new_state_value = json.loads(json_str) key = delta.client_token + state_name - previous_delta = LAST_DELTA_CACHE.get(key) - LAST_DELTA_CACHE[key] = new_state_value - if previous_delta is not None and not delta.flush: + cached = LAST_DELTA_CACHE.get(key) + hash_value = hash(json_str) + LAST_DELTA_CACHE[key] = DeltaCache(hash_value, delta) + if cached is not None and not delta.flush: full_delta[state_name] = { - "__patch": make_patch(previous_delta, new_state_value).patch + "__patch": make_patch(cached.delta.data, new_state_value).patch, + "__previous_hash": cached.hash, + "__hash": hash_value, } else: - full_delta[state_name] = {"__full": new_state_value} + full_delta[state_name] = { + "__full": new_state_value, + "__hash": hash_value, + } return full_delta return { state_name: {"__full": state_value} for state_name, state_value in delta.items() From 91aaeca093908f51ea7e813b93ce3412878d592d Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 3 Mar 2025 13:42:16 -0800 Subject: [PATCH 07/14] add partial hydrate event --- reflex/.templates/web/utils/state.js | 23 ++++++----- reflex/compiler/utils.py | 4 +- reflex/constants/compiler.py | 2 + reflex/event.py | 12 ++++++ reflex/middleware/hydrate_middleware.py | 51 ++++++++++++++++++++++++- reflex/state.py | 22 ++--------- 6 files changed, 81 insertions(+), 33 deletions(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 72b86a48a51..2f70d323321 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -471,12 +471,7 @@ export const connect = async ( const getSubstateFromUpdate = (update, substate_name) => { if (update.__patch) { if (last_substate_hash[substate_name] !== update.__previous_hash) { - throw new Error( - "Patch received out of order" + - update.__hash + - " " + - last_substate_hash[substate_name] - ); + throw new Error("Patch received out of order"); } last_substate_hash[substate_name] = update.__hash; return applyPatch(last_substate_info, update.__patch).newDocument; @@ -488,14 +483,22 @@ export const connect = async ( // On each received message, queue the updates and events. socket.current.on("event", async (update) => { + const failed_states = []; for (const substate in update.delta) { - const new_substate_info = getSubstateFromUpdate( - update.delta[substate], - substate - ); + try { + const new_substate_info = getSubstateFromUpdate( + update.delta[substate], + substate + ); + } catch (e) { + console.error("Received patch out of order", e); + states_failed.push(substate); + continue; + } last_substate_info[substate] = new_substate_info; dispatch[substate](new_substate_info); } + // TODO: Handle failed states applyClientStorageDelta(client_storage, update.delta); event_processing = !update.final; if (update.events) { diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index ce3d0c2e0b3..a5d386270ad 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -202,10 +202,10 @@ def compile_state(state: Type[BaseState]) -> dict: console.warn( f"Had to get initial state in a thread 🤮 {resolved_initial_state}", ) - return resolved_initial_state.data + return dict(**resolved_initial_state.data) # Normally the compile runs before any event loop starts, we asyncio.run is available for calling. - return asyncio.run(_resolve_delta(initial_state)).data + return dict(**asyncio.run(_resolve_delta(initial_state)).data) def _compile_client_storage_field( diff --git a/reflex/constants/compiler.py b/reflex/constants/compiler.py index 40134c15bba..93e25589ad0 100644 --- a/reflex/constants/compiler.py +++ b/reflex/constants/compiler.py @@ -55,6 +55,8 @@ class CompileVars(SimpleNamespace): EVENTS = "events" # The name of the initial hydrate event. HYDRATE = "hydrate" + # The name of the partial hydrate event. + PARTIAL_HYDRATE = "partial_hydrate" # The name of the is_hydrated variable. IS_HYDRATED = "is_hydrated" # The name of the function to add events to the queue. diff --git a/reflex/event.py b/reflex/event.py index 147d2e565f0..fc22684e07e 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -1194,6 +1194,18 @@ def get_hydrate_event(state: BaseState) -> str: return get_event(state, constants.CompileVars.HYDRATE) +def get_partial_hydrate_event(state: BaseState) -> str: + """Get the name of the partial hydrate event for the state. + + Args: + state: The state. + + Returns: + The name of the partial hydrate event. + """ + return get_event(state, constants.CompileVars.PARTIAL_HYDRATE) + + def call_event_handler( event_callback: EventHandler | EventSpec, event_spec: ArgsSpec | Sequence[ArgsSpec], diff --git a/reflex/middleware/hydrate_middleware.py b/reflex/middleware/hydrate_middleware.py index e88c6ca1ac5..9011a49abb2 100644 --- a/reflex/middleware/hydrate_middleware.py +++ b/reflex/middleware/hydrate_middleware.py @@ -3,10 +3,10 @@ from __future__ import annotations import dataclasses -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, ChainMap from reflex import constants -from reflex.event import Event, get_hydrate_event +from reflex.event import Event, get_hydrate_event, get_partial_hydrate_event from reflex.middleware.middleware import Middleware from reflex.state import BaseState, StateDelta, StateUpdate, _resolve_delta @@ -54,3 +54,50 @@ async def preprocess( # Return the state update. return StateUpdate(delta=delta, events=[]) + + +@dataclasses.dataclass(init=True) +class PartialHyderateMiddleware(Middleware): + """Middleware to handle partial app hydration.""" + + async def preprocess( + self, app: App, state: BaseState, event: Event + ) -> StateUpdate | None: + """Preprocess the event. + + Args: + app: The app to apply the middleware to." + state: The client state."" + event: The event to preprocess."" + + Returns: + An optional delta or list of state updates to return."" + """ + # If this is not the partial hydrate event, return None + if event.name != get_partial_hydrate_event(state): + return None + + substates_names = event.payload.get("states", []) + if not substates_names: + return None + + substates = [ + substate + for substate_name in substates_names + if (substate := state.get_substate(substate_name)) is not None + ] + + delta = await _resolve_delta( + StateDelta( + ChainMap(*[substate.dict() for substate in substates]), + client_token=state.router.session.client_token, + flush=True, + ) + ) + + # since a full dict was captured, clean any dirtiness + for substate in substates: + substate._clean() + + # Return the state update. + return StateUpdate(delta=delta, events=[]) diff --git a/reflex/state.py b/reflex/state.py index 8bb2ce3bf89..272386f757f 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -26,6 +26,7 @@ Callable, ClassVar, Dict, + Mapping, NamedTuple, Optional, Sequence, @@ -117,7 +118,7 @@ class StateDelta: """A dictionary representing the state delta.""" - data: dict[str, Any] = dataclasses.field(default_factory=dict) + data: Mapping[str, Any] = dataclasses.field(default_factory=dict) client_token: str | None = dataclasses.field(default=None) flush: bool = dataclasses.field(default=False) @@ -132,23 +133,6 @@ def __getitem__(self, key: str) -> Any: """ return self.data[key] - def __setitem__(self, key: str, value: Any): - """Set the item in the delta. - - Args: - key: The key to set. - value: The value to set. - """ - self.data[key] = value - - def __delitem__(self, key: str): - """Delete the item from the delta. - - Args: - key: The key to delete. - """ - del self.data[key] - def __iter__(self) -> Any: """Iterate over the delta. @@ -190,7 +174,7 @@ def __reversed__(self): Returns: The reversed delta. """ - return reversed(self.data) + return reversed(dict(**self.data)) def values(self): """Get the values of the delta. From 859e8be2dc12dd69ee2c79c72b1acd34818d21f6 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 3 Mar 2025 15:25:21 -0800 Subject: [PATCH 08/14] fix it ig --- reflex/.templates/web/utils/state.js | 34 +++++++++++++++---------- reflex/app_mixins/middleware.py | 2 ++ reflex/middleware/__init__.py | 5 ++-- reflex/middleware/hydrate_middleware.py | 2 +- reflex/state.py | 15 +++++------ 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 2f70d323321..0db209968b7 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -128,7 +128,7 @@ export const isStateful = () => { if (event_queue.length === 0) { return false; } - return event_queue.some((event) => event.name.startsWith("reflex___state")); + return event_queue.some((event) => event.name.startsWith(state_name)); }; /** @@ -471,10 +471,11 @@ export const connect = async ( const getSubstateFromUpdate = (update, substate_name) => { if (update.__patch) { if (last_substate_hash[substate_name] !== update.__previous_hash) { - throw new Error("Patch received out of order"); + return null; } last_substate_hash[substate_name] = update.__hash; - return applyPatch(last_substate_info, update.__patch).newDocument; + return applyPatch(last_substate_info[substate_name], update.__patch) + .newDocument; } else { last_substate_hash[substate_name] = update.__hash; return update.__full; @@ -483,24 +484,29 @@ export const connect = async ( // On each received message, queue the updates and events. socket.current.on("event", async (update) => { - const failed_states = []; + const failed_substates = []; for (const substate in update.delta) { - try { - const new_substate_info = getSubstateFromUpdate( - update.delta[substate], - substate - ); - } catch (e) { - console.error("Received patch out of order", e); - states_failed.push(substate); + const new_substate_info = getSubstateFromUpdate( + update.delta[substate], + substate + ); + if (new_substate_info === null) { + console.error("Received patch out of order", update.delta[substate]); + failed_substates.push(substate); + delete update.delta[substate]; continue; } last_substate_info[substate] = new_substate_info; + update.delta[substate] = new_substate_info; dispatch[substate](new_substate_info); } - // TODO: Handle failed states applyClientStorageDelta(client_storage, update.delta); event_processing = !update.final; + if (failed_substates.length > 0) { + update.events.push( + Event(state_name + ".partial_hydrate", { states: failed_substates }) + ); + } if (update.events) { queueEvents(update.events, socket); } @@ -919,7 +925,7 @@ export const useEventLoop = ( // Route after the initial page hydration. useEffect(() => { const change_start = () => { - const main_state_dispatch = dispatch["reflex___state____state"]; + const main_state_dispatch = dispatch[state_name]; if (main_state_dispatch !== undefined) { main_state_dispatch({ is_hydrated: false }); } diff --git a/reflex/app_mixins/middleware.py b/reflex/app_mixins/middleware.py index 0ace7cd844f..3f9071746c8 100644 --- a/reflex/app_mixins/middleware.py +++ b/reflex/app_mixins/middleware.py @@ -7,6 +7,7 @@ from reflex.event import Event from reflex.middleware import HydrateMiddleware, Middleware +from reflex.middleware.hydrate_middleware import PartialHyderateMiddleware from reflex.state import BaseState, StateUpdate from .mixin import AppMixin @@ -21,6 +22,7 @@ class MiddlewareMixin(AppMixin): def _init_mixin(self): self.middleware.append(HydrateMiddleware()) + self.middleware.append(PartialHyderateMiddleware()) def add_middleware(self, middleware: Middleware, index: int | None = None): """Add middleware to the app. diff --git a/reflex/middleware/__init__.py b/reflex/middleware/__init__.py index 8ba85b41a90..20c3e59747e 100644 --- a/reflex/middleware/__init__.py +++ b/reflex/middleware/__init__.py @@ -1,4 +1,5 @@ """Reflex middleware.""" -from .hydrate_middleware import HydrateMiddleware -from .middleware import Middleware +from .hydrate_middleware import HydrateMiddleware as HydrateMiddleware +from .hydrate_middleware import PartialHyderateMiddleware as PartialHydrateMiddleware +from .middleware import Middleware as Middleware diff --git a/reflex/middleware/hydrate_middleware.py b/reflex/middleware/hydrate_middleware.py index 9011a49abb2..656afdb7281 100644 --- a/reflex/middleware/hydrate_middleware.py +++ b/reflex/middleware/hydrate_middleware.py @@ -84,7 +84,7 @@ async def preprocess( substates = [ substate for substate_name in substates_names - if (substate := state.get_substate(substate_name)) is not None + if (substate := state.get_substate(substate_name.split("."))) is not None ] delta = await _resolve_delta( diff --git a/reflex/state.py b/reflex/state.py index 272386f757f..1af065a0c18 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -197,7 +197,7 @@ class DeltaCache(NamedTuple): """A named tuple representing the delta cache.""" hash: int - delta: StateDelta + delta: dict[str, Any] LAST_DELTA_CACHE: dict[str, DeltaCache] = {} @@ -221,10 +221,13 @@ def serialize_state_delta(delta: StateDelta) -> dict[str, Any]: key = delta.client_token + state_name cached = LAST_DELTA_CACHE.get(key) hash_value = hash(json_str) - LAST_DELTA_CACHE[key] = DeltaCache(hash_value, delta) + LAST_DELTA_CACHE[key] = DeltaCache(hash_value, new_state_value) if cached is not None and not delta.flush: + patch = make_patch(cached.delta, new_state_value).patch + if not patch: + continue full_delta[state_name] = { - "__patch": make_patch(cached.delta.data, new_state_value).patch, + "__patch": patch, "__previous_hash": cached.hash, "__hash": hash_value, } @@ -2031,11 +2034,7 @@ def get_delta(self, *, token: str | None = None) -> StateDelta: name for name, cv in self.computed_vars.items() if not cv._backend } - # Return the dirty vars for this instance, any cached/dependent computed vars, - # and always dirty computed vars (cache=False) - delta_vars = self.dirty_vars.intersection(self.base_vars).union( - self.dirty_vars.intersection(frontend_computed_vars) - ) + delta_vars = frontend_computed_vars.union(self.base_vars) subdelta: dict[str, Any] = { prop: self.get_value(prop) From 422911cfce9340e7bf68fcc86865334c03c24bcf Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 3 Mar 2025 15:37:19 -0800 Subject: [PATCH 09/14] don't include clean vars in diff --- reflex/state.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reflex/state.py b/reflex/state.py index 1af065a0c18..3ed9a62debb 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -2035,6 +2035,8 @@ def get_delta(self, *, token: str | None = None) -> StateDelta: } delta_vars = frontend_computed_vars.union(self.base_vars) + if not environment.REFLEX_USE_JSON_PATCH.get(): + delta_vars = self.dirty_vars.intersection(delta_vars) subdelta: dict[str, Any] = { prop: self.get_value(prop) From 7c4780af1a2ee8b8b08ab441e7c14830873c8d91 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 3 Mar 2025 15:44:34 -0800 Subject: [PATCH 10/14] test default app would include partial hydrate middleware --- tests/units/test_app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/units/test_app.py b/tests/units/test_app.py index e588f601a24..3e838199466 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -35,6 +35,7 @@ from reflex.components.radix.themes.typography.text import Text from reflex.event import Event from reflex.middleware import HydrateMiddleware +from reflex.middleware.hydrate_middleware import PartialHyderateMiddleware from reflex.model import Model from reflex.state import ( BaseState, @@ -211,7 +212,7 @@ def test_default_app(app: App): Args: app: The app to test. """ - assert app.middleware == [HydrateMiddleware()] + assert app.middleware == [HydrateMiddleware(), PartialHyderateMiddleware()] assert app.style == Style() assert app.admin_dash is None From be17639a7fd315a20bb62a41da0ee773b76c9785 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 3 Mar 2025 18:18:05 -0800 Subject: [PATCH 11/14] don't set _state --- reflex/.templates/web/utils/state.js | 1 + tests/integration/test_background_task.py | 2 +- tests/integration/test_call_script.py | 2 +- tests/integration/test_client_storage.py | 2 +- tests/integration/test_component_state.py | 2 +- tests/integration/test_connection_banner.py | 2 +- tests/integration/test_deploy_url.py | 2 +- tests/integration/test_dynamic_routes.py | 2 +- tests/integration/test_event_actions.py | 2 +- tests/integration/test_event_chain.py | 2 +- tests/integration/test_exception_handlers.py | 2 +- tests/integration/test_extra_overlay_function.py | 2 +- tests/integration/test_form_submit.py | 4 ++-- tests/integration/test_input.py | 2 +- tests/integration/test_login_flow.py | 2 +- tests/integration/test_server_side_event.py | 2 +- tests/integration/test_upload.py | 2 +- tests/integration/test_var_operations.py | 2 +- .../integration/tests_playwright/test_datetime_operations.py | 2 +- tests/integration/tests_playwright/test_table.py | 2 +- 20 files changed, 21 insertions(+), 20 deletions(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 0db209968b7..f88864ba262 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -886,6 +886,7 @@ export const useEventLoop = ( (async () => { // Process all outstanding events. while (event_queue.length > 0 && !event_processing) { + await new Promise((resolve) => setTimeout(resolve, 0)); await processEvent(socket.current); } })(); diff --git a/tests/integration/test_background_task.py b/tests/integration/test_background_task.py index 91a1b5ae15e..31ed03dea73 100644 --- a/tests/integration/test_background_task.py +++ b/tests/integration/test_background_task.py @@ -176,7 +176,7 @@ def index() -> rx.Component: rx.button("Reset", on_click=State.reset_counter, id="reset"), ) - app = rx.App(_state=rx.State) + app = rx.App() app.add_page(index) diff --git a/tests/integration/test_call_script.py b/tests/integration/test_call_script.py index 8236bf8e736..f3ee5dae307 100644 --- a/tests/integration/test_call_script.py +++ b/tests/integration/test_call_script.py @@ -187,7 +187,7 @@ def reset_(self): yield rx.call_script("inline_counter = 0; external_counter = 0") self.reset() - app = rx.App(_state=rx.State) + app = rx.App() Path("assets/external.js").write_text(external_scripts) @app.add_page diff --git a/tests/integration/test_client_storage.py b/tests/integration/test_client_storage.py index 3618c779dc1..fcfd09ddb0b 100644 --- a/tests/integration/test_client_storage.py +++ b/tests/integration/test_client_storage.py @@ -127,7 +127,7 @@ def index(): rx.box(ClientSideSubSubState.s1s, id="s1s"), ) - app = rx.App(_state=rx.State) + app = rx.App() app.add_page(index) app.add_page(index, route="/foo") diff --git a/tests/integration/test_component_state.py b/tests/integration/test_component_state.py index 654dc7ce9de..4f8decd9be4 100644 --- a/tests/integration/test_component_state.py +++ b/tests/integration/test_component_state.py @@ -72,7 +72,7 @@ def increment(self): State=_Counter, ) - app = rx.App(_state=rx.State) # noqa: F841 + app = rx.App() # noqa: F841 @rx.page() def index(): diff --git a/tests/integration/test_connection_banner.py b/tests/integration/test_connection_banner.py index f7fd7365cc1..0ce33ed5746 100644 --- a/tests/integration/test_connection_banner.py +++ b/tests/integration/test_connection_banner.py @@ -39,7 +39,7 @@ def index(): rx.button("Delay", id="delay", on_click=State.delay), ) - app = rx.App(_state=rx.State) + app = rx.App() app.add_page(index) diff --git a/tests/integration/test_deploy_url.py b/tests/integration/test_deploy_url.py index 207f3760984..936f97a185b 100644 --- a/tests/integration/test_deploy_url.py +++ b/tests/integration/test_deploy_url.py @@ -26,7 +26,7 @@ def index(): rx.button("GOTO SELF", on_click=State.goto_self, id="goto_self") ) - app = rx.App(_state=rx.State) + app = rx.App() app.add_page(index) diff --git a/tests/integration/test_dynamic_routes.py b/tests/integration/test_dynamic_routes.py index 9cdb970ca80..0be90b6e236 100644 --- a/tests/integration/test_dynamic_routes.py +++ b/tests/integration/test_dynamic_routes.py @@ -138,7 +138,7 @@ def arg() -> rx.Component: def redirect_page(): return rx.fragment(rx.text("redirecting...")) - app = rx.App(_state=rx.State) + app = rx.App() app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load) app.add_page(index, route="/static/x", on_load=DynamicState.on_load) app.add_page(index) diff --git a/tests/integration/test_event_actions.py b/tests/integration/test_event_actions.py index 358a083c875..1d15413312e 100644 --- a/tests/integration/test_event_actions.py +++ b/tests/integration/test_event_actions.py @@ -154,7 +154,7 @@ def index(): on_click=EventActionState.on_click("outer"), # pyright: ignore [reportCallIssue] ) - app = rx.App(_state=rx.State) + app = rx.App() app.add_page(index) diff --git a/tests/integration/test_event_chain.py b/tests/integration/test_event_chain.py index 755f64b20bd..dff099f538c 100644 --- a/tests/integration/test_event_chain.py +++ b/tests/integration/test_event_chain.py @@ -143,7 +143,7 @@ def click_yield_interim_value(self): time.sleep(0.5) self.interim_value = "final" - app = rx.App(_state=rx.State) + app = rx.App() token_input = rx.input( value=State.router.session.client_token, is_read_only=True, id="token" diff --git a/tests/integration/test_exception_handlers.py b/tests/integration/test_exception_handlers.py index 71858b8995b..1c15a6e72d8 100644 --- a/tests/integration/test_exception_handlers.py +++ b/tests/integration/test_exception_handlers.py @@ -39,7 +39,7 @@ def divide_by_number(self, number: int): """ print(1 / number) - app = rx.App(_state=rx.State) + app = rx.App() @app.add_page def index(): diff --git a/tests/integration/test_extra_overlay_function.py b/tests/integration/test_extra_overlay_function.py index 2e36057ca0b..bbaaa53f76c 100644 --- a/tests/integration/test_extra_overlay_function.py +++ b/tests/integration/test_extra_overlay_function.py @@ -25,7 +25,7 @@ def index(): ), ) - app = rx.App(_state=rx.State) + app = rx.App() app.add_page(index) diff --git a/tests/integration/test_form_submit.py b/tests/integration/test_form_submit.py index 69c55c05762..6f850607661 100644 --- a/tests/integration/test_form_submit.py +++ b/tests/integration/test_form_submit.py @@ -30,7 +30,7 @@ class FormState(rx.State): def form_submit(self, form_data: Dict): self.form_data = form_data - app = rx.App(_state=rx.State) + app = rx.App() @app.add_page def index(): @@ -90,7 +90,7 @@ class FormState(rx.State): def form_submit(self, form_data: Dict): self.form_data = form_data - app = rx.App(_state=rx.State) + app = rx.App() @app.add_page def index(): diff --git a/tests/integration/test_input.py b/tests/integration/test_input.py index 5f2948feb7a..eccfbfe7afa 100644 --- a/tests/integration/test_input.py +++ b/tests/integration/test_input.py @@ -16,7 +16,7 @@ def FullyControlledInput(): class State(rx.State): text: str = "initial" - app = rx.App(_state=rx.State) + app = rx.App() @app.add_page def index(): diff --git a/tests/integration/test_login_flow.py b/tests/integration/test_login_flow.py index a1df015bf49..9bcb1aba7a9 100644 --- a/tests/integration/test_login_flow.py +++ b/tests/integration/test_login_flow.py @@ -45,7 +45,7 @@ def login(): rx.button("Do it", on_click=State.login, id="doit"), ) - app = rx.App(_state=rx.State) + app = rx.App() app.add_page(index) app.add_page(login) diff --git a/tests/integration/test_server_side_event.py b/tests/integration/test_server_side_event.py index 3050a4e363d..547a4ea6453 100644 --- a/tests/integration/test_server_side_event.py +++ b/tests/integration/test_server_side_event.py @@ -38,7 +38,7 @@ def set_value_return(self): def set_value_return_c(self): return rx.set_value("c", "") - app = rx.App(_state=rx.State) + app = rx.App() @app.add_page def index(): diff --git a/tests/integration/test_upload.py b/tests/integration/test_upload.py index af359348c8b..a00015b64d7 100644 --- a/tests/integration/test_upload.py +++ b/tests/integration/test_upload.py @@ -166,7 +166,7 @@ def index(): rx.text(UploadState.event_order.to_string(), id="event-order"), ) - app = rx.App(_state=rx.State) + app = rx.App() app.add_page(index) diff --git a/tests/integration/test_var_operations.py b/tests/integration/test_var_operations.py index 35763556aa1..6f5a6572db3 100644 --- a/tests/integration/test_var_operations.py +++ b/tests/integration/test_var_operations.py @@ -44,7 +44,7 @@ class VarOperationState(rx.State): [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}] ) - app = rx.App(_state=rx.State) + app = rx.App() @rx.memo def memo_comp(list1: list[int], int_var1: int, id: str): diff --git a/tests/integration/tests_playwright/test_datetime_operations.py b/tests/integration/tests_playwright/test_datetime_operations.py index 2ac516d4acb..3e2c67d27cd 100644 --- a/tests/integration/tests_playwright/test_datetime_operations.py +++ b/tests/integration/tests_playwright/test_datetime_operations.py @@ -16,7 +16,7 @@ class DtOperationsState(rx.State): date2: datetime = datetime(2031, 1, 1) date3: datetime = datetime(2021, 1, 1) - app = rx.App(_state=DtOperationsState) + app = rx.App() @app.add_page def index(): diff --git a/tests/integration/tests_playwright/test_table.py b/tests/integration/tests_playwright/test_table.py index a88c4a621ad..060f9c0d387 100644 --- a/tests/integration/tests_playwright/test_table.py +++ b/tests/integration/tests_playwright/test_table.py @@ -20,7 +20,7 @@ def Table(): """App using table component.""" import reflex as rx - app = rx.App(_state=rx.State) + app = rx.App() @app.add_page def index(): From a8785e9922ec157102eb203a83a3ab4334374659 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 3 Mar 2025 18:26:05 -0800 Subject: [PATCH 12/14] set it to true to test things out --- reflex/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/config.py b/reflex/config.py index 87859c424d6..b67570c010c 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -719,7 +719,7 @@ class EnvironmentVariables: REFLEX_ADD_ALL_ROUTES_ENDPOINT: EnvVar[bool] = env_var(False) # Use the JSON patch format for websocket messages. - REFLEX_USE_JSON_PATCH: EnvVar[bool] = env_var(False) + REFLEX_USE_JSON_PATCH: EnvVar[bool] = env_var(True) environment = EnvironmentVariables() From c6fa89526fe3bf7b3c41a5c89078a86eb5ad5b04 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 17 Mar 2025 16:32:49 -0700 Subject: [PATCH 13/14] add json patch --- pyproject.toml | 1 + uv.lock | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7c1ec4677c1..641fd0a4b98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ dependencies = [ "gunicorn >=20.1.0,<24.0", "httpx >=0.25.1,<1.0", "jinja2 >=3.1.2,<4.0", + "jsonpatch >=1.33,<2.0", "lazy_loader >=0.4", "packaging >=23.1,<25.0", "platformdirs >=3.10.0,<5.0", diff --git a/uv.lock b/uv.lock index b4ee97f8888..ef509699fdc 100644 --- a/uv.lock +++ b/uv.lock @@ -713,6 +713,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, ] +[[package]] +name = "jsonpatch" +version = "1.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898 }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, +] + [[package]] name = "keyring" version = "25.6.0" @@ -1689,6 +1710,7 @@ dependencies = [ { name = "gunicorn" }, { name = "httpx" }, { name = "jinja2" }, + { name = "jsonpatch" }, { name = "lazy-loader" }, { name = "packaging" }, { name = "platformdirs" }, @@ -1750,6 +1772,7 @@ requires-dist = [ { name = "gunicorn", specifier = ">=20.1.0,<24.0" }, { name = "httpx", specifier = ">=0.25.1,<1.0" }, { name = "jinja2", specifier = ">=3.1.2,<4.0" }, + { name = "jsonpatch", specifier = ">=1.33,<2.0" }, { name = "lazy-loader", specifier = ">=0.4" }, { name = "packaging", specifier = ">=23.1,<25.0" }, { name = "platformdirs", specifier = ">=3.10.0,<5.0" }, From 690d029efb9387a0586edfda9db97138c975da12 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 17 Mar 2025 16:37:50 -0700 Subject: [PATCH 14/14] fix pyright --- reflex/.templates/web/utils/state.js | 36 ++-- tests/integration/test_event_actions.py | 227 ++++++++++++------------ 2 files changed, 134 insertions(+), 129 deletions(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 743af7c804a..f7e15e837fd 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -228,8 +228,8 @@ export const applyEvent = async (event, socket) => { a.href = eval?.( event.payload.url.replace( "getBackendURL(env.UPLOAD)", - `"${getBackendURL(env.UPLOAD)}"` - ) + `"${getBackendURL(env.UPLOAD)}"`, + ), ); } a.download = event.payload.filename; @@ -342,7 +342,7 @@ export const applyRestEvent = async (event, socket) => { event.payload.files, event.payload.upload_id, event.payload.on_upload_progress, - socket + socket, ); return false; } @@ -419,7 +419,7 @@ export const connect = async ( dispatch, transports, setConnectErrors, - client_storage = {} + client_storage = {}, ) => { // Get backend URL object from the endpoint. const endpoint = getBackendURL(EVENTURL); @@ -509,7 +509,7 @@ export const connect = async ( for (const substate in update.delta) { const new_substate_info = getSubstateFromUpdate( update.delta[substate], - substate + substate, ); if (new_substate_info === null) { console.error("Received patch out of order", update.delta[substate]); @@ -525,7 +525,7 @@ export const connect = async ( event_processing = !update.final; if (failed_substates.length > 0) { update.events.push( - Event(state_name + ".partial_hydrate", { states: failed_substates }) + Event(state_name + ".partial_hydrate", { states: failed_substates }), ); } if (update.events) { @@ -556,7 +556,7 @@ export const uploadFiles = async ( files, upload_id, on_upload_progress, - socket + socket, ) => { // return if there's no file to upload if (files === undefined || files.length === 0) { @@ -661,7 +661,7 @@ export const Event = ( name, payload = {}, event_actions = {}, - handler = null + handler = null, ) => { return { name, payload, handler, event_actions }; }; @@ -688,7 +688,7 @@ export const hydrateClientStorage = (client_storage) => { for (const state_key in client_storage.local_storage) { const options = client_storage.local_storage[state_key]; const local_storage_value = localStorage.getItem( - options.name || state_key + options.name || state_key, ); if (local_storage_value !== null) { client_storage_values[state_key] = local_storage_value; @@ -699,7 +699,7 @@ export const hydrateClientStorage = (client_storage) => { for (const state_key in client_storage.session_storage) { const session_options = client_storage.session_storage[state_key]; const session_storage_value = sessionStorage.getItem( - session_options.name || state_key + session_options.name || state_key, ); if (session_storage_value != null) { client_storage_values[state_key] = session_storage_value; @@ -724,7 +724,7 @@ export const hydrateClientStorage = (client_storage) => { const applyClientStorageDelta = (client_storage, delta) => { // find the main state and check for is_hydrated const unqualified_states = Object.keys(delta).filter( - (key) => key.split(".").length === 1 + (key) => key.split(".").length === 1, ); if (unqualified_states.length === 1) { const main_state = delta[unqualified_states[0]]; @@ -758,7 +758,7 @@ const applyClientStorageDelta = (client_storage, delta) => { const session_options = client_storage.session_storage[state_key]; sessionStorage.setItem( session_options.name || state_key, - delta[substate][key] + delta[substate][key], ); } } @@ -778,7 +778,7 @@ const applyClientStorageDelta = (client_storage, delta) => { export const useEventLoop = ( dispatch, initial_events = () => [], - client_storage = {} + client_storage = {}, ) => { const socket = useRef(null); const router = useRouter(); @@ -792,7 +792,7 @@ export const useEventLoop = ( event_actions = events.reduce( (acc, e) => ({ ...acc, ...e.event_actions }), - event_actions ?? {} + event_actions ?? {}, ); const _e = args.filter((o) => o?.preventDefault !== undefined)[0]; @@ -820,7 +820,7 @@ export const useEventLoop = ( debounce( combined_name, () => queueEvents(events, socket), - event_actions.debounce + event_actions.debounce, ); } else { queueEvents(events, socket); @@ -886,7 +886,7 @@ export const useEventLoop = ( dispatch, ["websocket"], setConnectErrors, - client_storage + client_storage, ); } } @@ -935,7 +935,7 @@ export const useEventLoop = ( vars[storage_to_state_map[e.key]] = e.newValue; const event = Event( `${state_name}.reflex___state____update_vars_internal_state.update_vars_internal`, - { vars: vars } + { vars: vars }, ); addEvents([event], e); } @@ -1028,7 +1028,7 @@ export const getRefValues = (refs) => { return refs.map((ref) => ref.current ? ref.current.value || ref.current.getAttribute("aria-valuenow") - : null + : null, ); }; diff --git a/tests/integration/test_event_actions.py b/tests/integration/test_event_actions.py index de0518d49c3..24d7469d9c6 100644 --- a/tests/integration/test_event_actions.py +++ b/tests/integration/test_event_actions.py @@ -59,124 +59,129 @@ def _get_custom_code(self) -> str | None: def get_event_triggers(self): return {"on_click": lambda: []} - def index(): - return rx.vstack( - rx.input( - value=EventActionState.router.session.client_token, - is_read_only=True, - id="token", - ), - rx.button("No events", id="btn-no-events"), - rx.button( - "Stop Prop Only", - id="btn-stop-prop-only", - on_click=rx.stop_propagation, # pyright: ignore [reportArgumentType] - ), - rx.button( - "Click event", - on_click=EventActionState.on_click("no_event_actions"), # pyright: ignore [reportCallIssue] - id="btn-click-event", - ), - rx.button( - "Click stop propagation", - on_click=EventActionState.on_click("stop_propagation").stop_propagation, # pyright: ignore [reportCallIssue] - id="btn-click-stop-propagation", - ), - rx.button( - "Click stop propagation2", - on_click=EventActionState.on_click2.stop_propagation, - id="btn-click-stop-propagation2", - ), - rx.button( - "Click event 2", - on_click=EventActionState.on_click2, - id="btn-click-event2", - ), - rx.link( - "Link", - href="#", - on_click=EventActionState.on_click("link_no_event_actions"), # pyright: ignore [reportCallIssue] - id="link", - ), - rx.link( - "Link Stop Propagation", - href="#", - on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] - "link_stop_propagation" - ).stop_propagation, - id="link-stop-propagation", - ), - rx.link( - "Link Prevent Default Only", - href="/invalid", - on_click=rx.prevent_default, # pyright: ignore [reportArgumentType] - id="link-prevent-default-only", - ), - rx.link( - "Link Prevent Default", - href="/invalid", - on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] - "link_prevent_default" - ).prevent_default, - id="link-prevent-default", - ), - rx.link( - "Link Both", - href="/invalid", - on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] - "link_both" - ).stop_propagation.prevent_default, - id="link-stop-propagation-prevent-default", - ), - EventFiringComponent.create( - id="custom-stop-propagation", - on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] - "custom-stop-propagation" - ).stop_propagation, - ), - EventFiringComponent.create( - id="custom-prevent-default", - on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] - "custom-prevent-default" - ).prevent_default, - ), - rx.button( - "Throttle", - id="btn-throttle", - on_click=lambda: EventActionState.on_click_throttle.throttle( # pyright: ignore [reportFunctionMemberAccess] - 200 - ).stop_propagation, - ), - rx.button( - "Debounce", - id="btn-debounce", - on_click=EventActionState.on_click_debounce.debounce( # pyright: ignore [reportFunctionMemberAccess] - 200 - ).stop_propagation, - ), - rx.list( # pyright: ignore [reportAttributeAccessIssue] - rx.foreach( - EventActionState.order, - rx.list_item, + def index() -> rx.Component: + return rx.fragment( + rx.vstack( + rx.input( + value=EventActionState.router.session.client_token, + is_read_only=True, + id="token", ), - ), - on_click=EventActionState.on_click("outer"), # pyright: ignore [reportCallIssue] - ), rx.form( - rx.dialog.root( - rx.dialog.trigger( - rx.button("Open Dialog", type="button", id="btn-dialog"), + rx.button("No events", id="btn-no-events"), + rx.button( + "Stop Prop Only", + id="btn-stop-prop-only", on_click=rx.stop_propagation, # pyright: ignore [reportArgumentType] ), - rx.dialog.content( - rx.dialog.close( - rx.form( - rx.button("Submit", id="btn-submit"), - on_submit=EventActionState.on_submit.stop_propagation, # pyright: ignore [reportCallIssue] + rx.button( + "Click event", + on_click=EventActionState.on_click("no_event_actions"), # pyright: ignore [reportCallIssue] + id="btn-click-event", + ), + rx.button( + "Click stop propagation", + on_click=EventActionState.on_click( + "stop_propagation" + ).stop_propagation, # pyright: ignore [reportCallIssue] + id="btn-click-stop-propagation", + ), + rx.button( + "Click stop propagation2", + on_click=EventActionState.on_click2.stop_propagation, + id="btn-click-stop-propagation2", + ), + rx.button( + "Click event 2", + on_click=EventActionState.on_click2, + id="btn-click-event2", + ), + rx.link( + "Link", + href="#", + on_click=EventActionState.on_click("link_no_event_actions"), # pyright: ignore [reportCallIssue] + id="link", + ), + rx.link( + "Link Stop Propagation", + href="#", + on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] + "link_stop_propagation" + ).stop_propagation, + id="link-stop-propagation", + ), + rx.link( + "Link Prevent Default Only", + href="/invalid", + on_click=rx.prevent_default, # pyright: ignore [reportArgumentType] + id="link-prevent-default-only", + ), + rx.link( + "Link Prevent Default", + href="/invalid", + on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] + "link_prevent_default" + ).prevent_default, + id="link-prevent-default", + ), + rx.link( + "Link Both", + href="/invalid", + on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] + "link_both" + ).stop_propagation.prevent_default, + id="link-stop-propagation-prevent-default", + ), + EventFiringComponent.create( + id="custom-stop-propagation", + on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] + "custom-stop-propagation" + ).stop_propagation, + ), + EventFiringComponent.create( + id="custom-prevent-default", + on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] + "custom-prevent-default" + ).prevent_default, + ), + rx.button( + "Throttle", + id="btn-throttle", + on_click=lambda: EventActionState.on_click_throttle.throttle( # pyright: ignore [reportFunctionMemberAccess] + 200 + ).stop_propagation, + ), + rx.button( + "Debounce", + id="btn-debounce", + on_click=EventActionState.on_click_debounce.debounce( # pyright: ignore [reportFunctionMemberAccess] + 200 + ).stop_propagation, + ), + rx.list( # pyright: ignore [reportAttributeAccessIssue] + rx.foreach( + EventActionState.order, + rx.list_item, + ), + ), + on_click=EventActionState.on_click("outer"), # pyright: ignore [reportCallIssue] + ), + rx.form( + rx.dialog.root( + rx.dialog.trigger( + rx.button("Open Dialog", type="button", id="btn-dialog"), + on_click=rx.stop_propagation, # pyright: ignore [reportArgumentType] + ), + rx.dialog.content( + rx.dialog.close( + rx.form( + rx.button("Submit", id="btn-submit"), + on_submit=EventActionState.on_submit.stop_propagation, # pyright: ignore [reportCallIssue] + ), ), ), ), + on_submit=EventActionState.on_submit, # pyright: ignore [reportCallIssue] ), - on_submit=EventActionState.on_submit, # pyright: ignore [reportCallIssue] ) app = rx.App()