fix merge conflicts

Merge remote-tracking branch 'upstream/main' into hybrid-properties
This commit is contained in:
Benedikt Bartscher 2024-09-05 19:48:02 +02:00
commit dbec527475
No known key found for this signature in database
86 changed files with 1915 additions and 4904 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
assets/external/*
dist/*
examples/
.web
.idea
.vscode
.coverage

486
poetry.lock generated
View File

@ -151,13 +151,13 @@ virtualenv = ["virtualenv (>=20.0.35)"]
[[package]]
name = "certifi"
version = "2024.7.4"
version = "2024.8.30"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
{file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
]
[[package]]
@ -582,22 +582,23 @@ test = ["pytest (>=6)"]
[[package]]
name = "fastapi"
version = "0.110.3"
version = "0.112.2"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.8"
files = [
{file = "fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32"},
{file = "fastapi-0.110.3.tar.gz", hash = "sha256:555700b0159379e94fdbfc6bb66a0f1c43f4cf7060f25239af3d84b63a656626"},
{file = "fastapi-0.112.2-py3-none-any.whl", hash = "sha256:db84b470bd0e2b1075942231e90e3577e12a903c4dc8696f0d206a7904a7af1c"},
{file = "fastapi-0.112.2.tar.gz", hash = "sha256:3d4729c038414d5193840706907a41839d839523da6ed0c2811f1168cac1798c"},
]
[package.dependencies]
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
starlette = ">=0.37.2,<0.38.0"
starlette = ">=0.37.2,<0.39.0"
typing-extensions = ">=4.8.0"
[package.extras]
all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"]
[[package]]
name = "filelock"
@ -688,13 +689,13 @@ test = ["objgraph", "psutil"]
[[package]]
name = "gunicorn"
version = "22.0.0"
version = "23.0.0"
description = "WSGI HTTP Server for UNIX"
optional = false
python-versions = ">=3.7"
files = [
{file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"},
{file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"},
{file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"},
{file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"},
]
[package.dependencies]
@ -741,13 +742,13 @@ trio = ["trio (>=0.22.0,<0.26.0)"]
[[package]]
name = "httpx"
version = "0.27.0"
version = "0.27.2"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
{file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"},
{file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
]
[package.dependencies]
@ -762,6 +763,7 @@ brotli = ["brotli", "brotlicffi"]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "identify"
@ -1180,56 +1182,56 @@ files = [
[[package]]
name = "numpy"
version = "2.0.1"
version = "2.0.2"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.9"
files = [
{file = "numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134"},
{file = "numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42"},
{file = "numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02"},
{file = "numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101"},
{file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9"},
{file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015"},
{file = "numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87"},
{file = "numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82"},
{file = "numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1"},
{file = "numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868"},
{file = "numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268"},
{file = "numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e"},
{file = "numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343"},
{file = "numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b"},
{file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe"},
{file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67"},
{file = "numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7"},
{file = "numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55"},
{file = "numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4"},
{file = "numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8"},
{file = "numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"},
{file = "numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"},
{file = "numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"},
{file = "numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"},
{file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"},
{file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"},
{file = "numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"},
{file = "numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"},
{file = "numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"},
{file = "numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"},
{file = "numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414"},
{file = "numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef"},
{file = "numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06"},
{file = "numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c"},
{file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59"},
{file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26"},
{file = "numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018"},
{file = "numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c"},
{file = "numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"},
{file = "numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368"},
{file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f"},
{file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d"},
{file = "numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990"},
{file = "numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f"},
{file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"},
{file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"},
{file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"},
{file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"},
{file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"},
{file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"},
{file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"},
{file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"},
{file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"},
{file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"},
{file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"},
{file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"},
{file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"},
{file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"},
{file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"},
{file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"},
{file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"},
{file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"},
{file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"},
{file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"},
{file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"},
{file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"},
{file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"},
{file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"},
{file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"},
{file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"},
{file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"},
{file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"},
{file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"},
{file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"},
{file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"},
{file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"},
{file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"},
{file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"},
{file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"},
{file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"},
{file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"},
{file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"},
{file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"},
{file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"},
{file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"},
{file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"},
{file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"},
{file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"},
{file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"},
{file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"},
]
[[package]]
@ -1593,13 +1595,13 @@ type = ["mypy (>=1.8)"]
[[package]]
name = "plotly"
version = "5.23.0"
version = "5.24.0"
description = "An open-source, interactive data visualization library for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "plotly-5.23.0-py3-none-any.whl", hash = "sha256:76cbe78f75eddc10c56f5a4ee3e7ccaade7c0a57465546f02098c0caed6c2d1a"},
{file = "plotly-5.23.0.tar.gz", hash = "sha256:89e57d003a116303a34de6700862391367dd564222ab71f8531df70279fc0193"},
{file = "plotly-5.24.0-py3-none-any.whl", hash = "sha256:0e54efe52c8cef899f7daa41be9ed97dfb6be622613a2a8f56a86a0634b2b67e"},
{file = "plotly-5.24.0.tar.gz", hash = "sha256:eae9f4f54448682442c92c1e97148e3ad0c52f0cf86306e1b76daba24add554a"},
]
[package.dependencies]
@ -2239,13 +2241,13 @@ idna2008 = ["idna"]
[[package]]
name = "rich"
version = "13.7.1"
version = "13.8.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"},
{file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
{file = "rich-13.8.0-py3-none-any.whl", hash = "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc"},
{file = "rich-13.8.0.tar.gz", hash = "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4"},
]
[package.dependencies]
@ -2299,13 +2301,13 @@ jeepney = ">=0.6"
[[package]]
name = "selenium"
version = "4.23.1"
version = "4.24.0"
description = "Official Python bindings for Selenium WebDriver"
optional = false
python-versions = ">=3.8"
files = [
{file = "selenium-4.23.1-py3-none-any.whl", hash = "sha256:3a8d9f23dc636bd3840dd56f00c2739e32ec0c1e34a821dd553e15babef24477"},
{file = "selenium-4.23.1.tar.gz", hash = "sha256:128d099e66284437e7128d2279176ec7a06e6ec7426e167f5d34987166bd8f46"},
{file = "selenium-4.24.0-py3-none-any.whl", hash = "sha256:42c23f60753d5415b261b236cecbd69bd4eb5271e1563915f546b443cb6b71c6"},
{file = "selenium-4.24.0.tar.gz", hash = "sha256:88281e5b5b90fe231868905d5ea745b9ee5e30db280b33498cc73fb0fa06d571"},
]
[package.dependencies]
@ -2496,13 +2498,13 @@ SQLAlchemy = ">=2.0.14,<2.1.0"
[[package]]
name = "starlette"
version = "0.37.2"
version = "0.38.2"
description = "The little ASGI library that shines."
optional = false
python-versions = ">=3.8"
files = [
{file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"},
{file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"},
{file = "starlette-0.38.2-py3-none-any.whl", hash = "sha256:4ec6a59df6bbafdab5f567754481657f7ed90dc9d69b0c9ff017907dd54faeff"},
{file = "starlette-0.38.2.tar.gz", hash = "sha256:c7c0441065252160993a1a37cf2a73bb64d271b17303e0b0c1eb7191cfb12d75"},
]
[package.dependencies]
@ -2657,13 +2659,13 @@ urllib3 = ">=1.26.0"
[[package]]
name = "typer"
version = "0.12.4"
version = "0.12.5"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
python-versions = ">=3.7"
files = [
{file = "typer-0.12.4-py3-none-any.whl", hash = "sha256:819aa03699f438397e876aa12b0d63766864ecba1b579092cc9fe35d886e34b6"},
{file = "typer-0.12.4.tar.gz", hash = "sha256:c9c1613ed6a166162705b3347b8d10b661ccc5d95692654d0fb628118f2c34e6"},
{file = "typer-0.12.5-py3-none-any.whl", hash = "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b"},
{file = "typer-0.12.5.tar.gz", hash = "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722"},
]
[package.dependencies]
@ -2753,152 +2755,6 @@ platformdirs = ">=3.9.1,<5"
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)"]
[[package]]
name = "watchdog"
version = "4.0.2"
description = "Filesystem events monitoring"
optional = false
python-versions = ">=3.8"
files = [
{file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"},
{file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"},
{file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"},
{file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"},
{file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"},
{file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"},
{file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"},
{file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"},
{file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"},
{file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b"},
{file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d"},
{file = "watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7"},
{file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"},
{file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"},
{file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"},
{file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9"},
{file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578"},
{file = "watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b"},
{file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"},
{file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"},
{file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"},
{file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"},
{file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1"},
{file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"},
{file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"},
{file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"},
{file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"},
{file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"},
]
[package.extras]
watchmedo = ["PyYAML (>=3.10)"]
[[package]]
name = "watchfiles"
version = "0.23.0"
description = "Simple, modern and high performance file watching and code reload in python."
optional = false
python-versions = ">=3.8"
files = [
{file = "watchfiles-0.23.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bee8ce357a05c20db04f46c22be2d1a2c6a8ed365b325d08af94358e0688eeb4"},
{file = "watchfiles-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ccd3011cc7ee2f789af9ebe04745436371d36afe610028921cab9f24bb2987b"},
{file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb02d41c33be667e6135e6686f1bb76104c88a312a18faa0ef0262b5bf7f1a0f"},
{file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cf12ac34c444362f3261fb3ff548f0037ddd4c5bb85f66c4be30d2936beb3c5"},
{file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0b2c25040a3c0ce0e66c7779cc045fdfbbb8d59e5aabfe033000b42fe44b53e"},
{file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf2be4b9eece4f3da8ba5f244b9e51932ebc441c0867bd6af46a3d97eb068d6"},
{file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40cb8fa00028908211eb9f8d47744dca21a4be6766672e1ff3280bee320436f1"},
{file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f48c917ffd36ff9a5212614c2d0d585fa8b064ca7e66206fb5c095015bc8207"},
{file = "watchfiles-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9d183e3888ada88185ab17064079c0db8c17e32023f5c278d7bf8014713b1b5b"},
{file = "watchfiles-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9837edf328b2805346f91209b7e660f65fb0e9ca18b7459d075d58db082bf981"},
{file = "watchfiles-0.23.0-cp310-none-win32.whl", hash = "sha256:296e0b29ab0276ca59d82d2da22cbbdb39a23eed94cca69aed274595fb3dfe42"},
{file = "watchfiles-0.23.0-cp310-none-win_amd64.whl", hash = "sha256:4ea756e425ab2dfc8ef2a0cb87af8aa7ef7dfc6fc46c6f89bcf382121d4fff75"},
{file = "watchfiles-0.23.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e397b64f7aaf26915bf2ad0f1190f75c855d11eb111cc00f12f97430153c2eab"},
{file = "watchfiles-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4ac73b02ca1824ec0a7351588241fd3953748d3774694aa7ddb5e8e46aef3e3"},
{file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130a896d53b48a1cecccfa903f37a1d87dbb74295305f865a3e816452f6e49e4"},
{file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5e7803a65eb2d563c73230e9d693c6539e3c975ccfe62526cadde69f3fda0cf"},
{file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1aa4cc85202956d1a65c88d18c7b687b8319dbe6b1aec8969784ef7a10e7d1a"},
{file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87f889f6e58849ddb7c5d2cb19e2e074917ed1c6e3ceca50405775166492cca8"},
{file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37fd826dac84c6441615aa3f04077adcc5cac7194a021c9f0d69af20fb9fa788"},
{file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee7db6e36e7a2c15923072e41ea24d9a0cf39658cb0637ecc9307b09d28827e1"},
{file = "watchfiles-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2368c5371c17fdcb5a2ea71c5c9d49f9b128821bfee69503cc38eae00feb3220"},
{file = "watchfiles-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:857af85d445b9ba9178db95658c219dbd77b71b8264e66836a6eba4fbf49c320"},
{file = "watchfiles-0.23.0-cp311-none-win32.whl", hash = "sha256:1d636c8aeb28cdd04a4aa89030c4b48f8b2954d8483e5f989774fa441c0ed57b"},
{file = "watchfiles-0.23.0-cp311-none-win_amd64.whl", hash = "sha256:46f1d8069a95885ca529645cdbb05aea5837d799965676e1b2b1f95a4206313e"},
{file = "watchfiles-0.23.0-cp311-none-win_arm64.whl", hash = "sha256:e495ed2a7943503766c5d1ff05ae9212dc2ce1c0e30a80d4f0d84889298fa304"},
{file = "watchfiles-0.23.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1db691bad0243aed27c8354b12d60e8e266b75216ae99d33e927ff5238d270b5"},
{file = "watchfiles-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62d2b18cb1edaba311fbbfe83fb5e53a858ba37cacb01e69bc20553bb70911b8"},
{file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e087e8fdf1270d000913c12e6eca44edd02aad3559b3e6b8ef00f0ce76e0636f"},
{file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd41d5c72417b87c00b1b635738f3c283e737d75c5fa5c3e1c60cd03eac3af77"},
{file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e5f3ca0ff47940ce0a389457b35d6df601c317c1e1a9615981c474452f98de1"},
{file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6991e3a78f642368b8b1b669327eb6751439f9f7eaaa625fae67dd6070ecfa0b"},
{file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f7252f52a09f8fa5435dc82b6af79483118ce6bd51eb74e6269f05ee22a7b9f"},
{file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e01bcb8d767c58865207a6c2f2792ad763a0fe1119fb0a430f444f5b02a5ea0"},
{file = "watchfiles-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8e56fbcdd27fce061854ddec99e015dd779cae186eb36b14471fc9ae713b118c"},
{file = "watchfiles-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bd3e2d64500a6cad28bcd710ee6269fbeb2e5320525acd0cfab5f269ade68581"},
{file = "watchfiles-0.23.0-cp312-none-win32.whl", hash = "sha256:eb99c954291b2fad0eff98b490aa641e128fbc4a03b11c8a0086de8b7077fb75"},
{file = "watchfiles-0.23.0-cp312-none-win_amd64.whl", hash = "sha256:dccc858372a56080332ea89b78cfb18efb945da858fabeb67f5a44fa0bcb4ebb"},
{file = "watchfiles-0.23.0-cp312-none-win_arm64.whl", hash = "sha256:6c21a5467f35c61eafb4e394303720893066897fca937bade5b4f5877d350ff8"},
{file = "watchfiles-0.23.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ba31c32f6b4dceeb2be04f717811565159617e28d61a60bb616b6442027fd4b9"},
{file = "watchfiles-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85042ab91814fca99cec4678fc063fb46df4cbb57b4835a1cc2cb7a51e10250e"},
{file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24655e8c1c9c114005c3868a3d432c8aa595a786b8493500071e6a52f3d09217"},
{file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b1a950ab299a4a78fd6369a97b8763732bfb154fdb433356ec55a5bce9515c1"},
{file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8d3c5cd327dd6ce0edfc94374fb5883d254fe78a5e9d9dfc237a1897dc73cd1"},
{file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ff785af8bacdf0be863ec0c428e3288b817e82f3d0c1d652cd9c6d509020dd0"},
{file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02b7ba9d4557149410747353e7325010d48edcfe9d609a85cb450f17fd50dc3d"},
{file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a1b05c0afb2cd2f48c1ed2ae5487b116e34b93b13074ed3c22ad5c743109f0"},
{file = "watchfiles-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:109a61763e7318d9f821b878589e71229f97366fa6a5c7720687d367f3ab9eef"},
{file = "watchfiles-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9f8e6bb5ac007d4a4027b25f09827ed78cbbd5b9700fd6c54429278dacce05d1"},
{file = "watchfiles-0.23.0-cp313-none-win32.whl", hash = "sha256:f46c6f0aec8d02a52d97a583782d9af38c19a29900747eb048af358a9c1d8e5b"},
{file = "watchfiles-0.23.0-cp313-none-win_amd64.whl", hash = "sha256:f449afbb971df5c6faeb0a27bca0427d7b600dd8f4a068492faec18023f0dcff"},
{file = "watchfiles-0.23.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:2dddc2487d33e92f8b6222b5fb74ae2cfde5e8e6c44e0248d24ec23befdc5366"},
{file = "watchfiles-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e75695cc952e825fa3e0684a7f4a302f9128721f13eedd8dbd3af2ba450932b8"},
{file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2537ef60596511df79b91613a5bb499b63f46f01a11a81b0a2b0dedf645d0a9c"},
{file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20b423b58f5fdde704a226b598a2d78165fe29eb5621358fe57ea63f16f165c4"},
{file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b98732ec893975455708d6fc9a6daab527fc8bbe65be354a3861f8c450a632a4"},
{file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee1f5fcbf5bc33acc0be9dd31130bcba35d6d2302e4eceafafd7d9018c7755ab"},
{file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f195338a5a7b50a058522b39517c50238358d9ad8284fd92943643144c0c03"},
{file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524fcb8d59b0dbee2c9b32207084b67b2420f6431ed02c18bd191e6c575f5c48"},
{file = "watchfiles-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0eff099a4df36afaa0eea7a913aa64dcf2cbd4e7a4f319a73012210af4d23810"},
{file = "watchfiles-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a8323daae27ea290ba3350c70c836c0d2b0fb47897fa3b0ca6a5375b952b90d3"},
{file = "watchfiles-0.23.0-cp38-none-win32.whl", hash = "sha256:aafea64a3ae698695975251f4254df2225e2624185a69534e7fe70581066bc1b"},
{file = "watchfiles-0.23.0-cp38-none-win_amd64.whl", hash = "sha256:c846884b2e690ba62a51048a097acb6b5cd263d8bd91062cd6137e2880578472"},
{file = "watchfiles-0.23.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a753993635eccf1ecb185dedcc69d220dab41804272f45e4aef0a67e790c3eb3"},
{file = "watchfiles-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6bb91fa4d0b392f0f7e27c40981e46dda9eb0fbc84162c7fb478fe115944f491"},
{file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1f67312efa3902a8e8496bfa9824d3bec096ff83c4669ea555c6bdd213aa516"},
{file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ca6b71dcc50d320c88fb2d88ecd63924934a8abc1673683a242a7ca7d39e781"},
{file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aec5c29915caf08771d2507da3ac08e8de24a50f746eb1ed295584ba1820330"},
{file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1733b9bc2c8098c6bdb0ff7a3d7cb211753fecb7bd99bdd6df995621ee1a574b"},
{file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02ff5d7bd066c6a7673b17c8879cd8ee903078d184802a7ee851449c43521bdd"},
{file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18e2de19801b0eaa4c5292a223effb7cfb43904cb742c5317a0ac686ed604765"},
{file = "watchfiles-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8ada449e22198c31fb013ae7e9add887e8d2bd2335401abd3cbc55f8c5083647"},
{file = "watchfiles-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3af1b05361e1cc497bf1be654a664750ae61f5739e4bb094a2be86ec8c6db9b6"},
{file = "watchfiles-0.23.0-cp39-none-win32.whl", hash = "sha256:486bda18be5d25ab5d932699ceed918f68eb91f45d018b0343e3502e52866e5e"},
{file = "watchfiles-0.23.0-cp39-none-win_amd64.whl", hash = "sha256:d2d42254b189a346249424fb9bb39182a19289a2409051ee432fb2926bad966a"},
{file = "watchfiles-0.23.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6a9265cf87a5b70147bfb2fec14770ed5b11a5bb83353f0eee1c25a81af5abfe"},
{file = "watchfiles-0.23.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f02a259fcbbb5fcfe7a0805b1097ead5ba7a043e318eef1db59f93067f0b49b"},
{file = "watchfiles-0.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebaebb53b34690da0936c256c1cdb0914f24fb0e03da76d185806df9328abed"},
{file = "watchfiles-0.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd257f98cff9c6cb39eee1a83c7c3183970d8a8d23e8cf4f47d9a21329285cee"},
{file = "watchfiles-0.23.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aba037c1310dd108411d27b3d5815998ef0e83573e47d4219f45753c710f969f"},
{file = "watchfiles-0.23.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:a96ac14e184aa86dc43b8a22bb53854760a58b2966c2b41580de938e9bf26ed0"},
{file = "watchfiles-0.23.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11698bb2ea5e991d10f1f4f83a39a02f91e44e4bd05f01b5c1ec04c9342bf63c"},
{file = "watchfiles-0.23.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efadd40fca3a04063d40c4448c9303ce24dd6151dc162cfae4a2a060232ebdcb"},
{file = "watchfiles-0.23.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:556347b0abb4224c5ec688fc58214162e92a500323f50182f994f3ad33385dcb"},
{file = "watchfiles-0.23.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1cf7f486169986c4b9d34087f08ce56a35126600b6fef3028f19ca16d5889071"},
{file = "watchfiles-0.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f18de0f82c62c4197bea5ecf4389288ac755896aac734bd2cc44004c56e4ac47"},
{file = "watchfiles-0.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:532e1f2c491274d1333a814e4c5c2e8b92345d41b12dc806cf07aaff786beb66"},
{file = "watchfiles-0.23.0.tar.gz", hash = "sha256:9338ade39ff24f8086bb005d16c29f8e9f19e55b18dcb04dfa26fcbc09da497b"},
]
[package.dependencies]
anyio = ">=3.0.0"
[[package]]
name = "websocket-client"
version = "1.8.0"
@ -2917,97 +2773,97 @@ test = ["websockets"]
[[package]]
name = "websockets"
version = "13.0"
version = "13.0.1"
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
optional = false
python-versions = ">=3.8"
files = [
{file = "websockets-13.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad4fa707ff9e2ffee019e946257b5300a45137a58f41fbd9a4db8e684ab61528"},
{file = "websockets-13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6fd757f313c13c34dae9f126d3ba4cf97175859c719e57c6a614b781c86b617e"},
{file = "websockets-13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cbac2eb7ce0fac755fb983c9247c4a60c4019bcde4c0e4d167aeb17520cc7ef1"},
{file = "websockets-13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4b83cf7354cbbc058e97b3e545dceb75b8d9cf17fd5a19db419c319ddbaaf7a"},
{file = "websockets-13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9202c0010c78fad1041e1c5285232b6508d3633f92825687549540a70e9e5901"},
{file = "websockets-13.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6566e79c8c7cbea75ec450f6e1828945fc5c9a4769ceb1c7b6e22470539712"},
{file = "websockets-13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e7fcad070dcd9ad37a09d89a4cbc2a5e3e45080b88977c0da87b3090f9f55ead"},
{file = "websockets-13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a8f7d65358a25172db00c69bcc7df834155ee24229f560d035758fd6613111a"},
{file = "websockets-13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:63b702fb31e3f058f946ccdfa551f4d57a06f7729c369e8815eb18643099db37"},
{file = "websockets-13.0-cp310-cp310-win32.whl", hash = "sha256:3a20cf14ba7b482c4a1924b5e061729afb89c890ca9ed44ac4127c6c5986e424"},
{file = "websockets-13.0-cp310-cp310-win_amd64.whl", hash = "sha256:587245f0704d0bb675f919898d7473e8827a6d578e5a122a21756ca44b811ec8"},
{file = "websockets-13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:06df8306c241c235075d2ae77367038e701e53bc8c1bb4f6644f4f53aa6dedd0"},
{file = "websockets-13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85a1f92a02f0b8c1bf02699731a70a8a74402bb3f82bee36e7768b19a8ed9709"},
{file = "websockets-13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9ed02c604349068d46d87ef4c2012c112c791f2bec08671903a6bb2bd9c06784"},
{file = "websockets-13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b89849171b590107f6724a7b0790736daead40926ddf47eadf998b4ff51d6414"},
{file = "websockets-13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:939a16849d71203628157a5e4a495da63967c744e1e32018e9b9e2689aca64d4"},
{file = "websockets-13.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad818cdac37c0ad4c58e51cb4964eae4f18b43c4a83cb37170b0d90c31bd80cf"},
{file = "websockets-13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cbfe82a07596a044de78bb7a62519e71690c5812c26c5f1d4b877e64e4f46309"},
{file = "websockets-13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e07e76c49f39c5b45cbd7362b94f001ae209a3ea4905ae9a09cfd53b3c76373d"},
{file = "websockets-13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:372f46a0096cfda23c88f7e42349a33f8375e10912f712e6b496d3a9a557290f"},
{file = "websockets-13.0-cp311-cp311-win32.whl", hash = "sha256:376a43a4fd96725f13450d3d2e98f4f36c3525c562ab53d9a98dd2950dca9a8a"},
{file = "websockets-13.0-cp311-cp311-win_amd64.whl", hash = "sha256:2be1382a4daa61e2f3e2be3b3c86932a8db9d1f85297feb6e9df22f391f94452"},
{file = "websockets-13.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5407c34776b9b77bd89a5f95eb0a34aaf91889e3f911c63f13035220eb50107"},
{file = "websockets-13.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4782ec789f059f888c1e8fdf94383d0e64b531cffebbf26dd55afd53ab487ca4"},
{file = "websockets-13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c8feb8e19ef65c9994e652c5b0324abd657bedd0abeb946fb4f5163012c1e730"},
{file = "websockets-13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f3d2e20c442b58dbac593cb1e02bc02d149a86056cc4126d977ad902472e3b"},
{file = "websockets-13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e39d393e0ab5b8bd01717cc26f2922026050188947ff54fe6a49dc489f7750b7"},
{file = "websockets-13.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f661a4205741bdc88ac9c2b2ec003c72cee97e4acd156eb733662ff004ba429"},
{file = "websockets-13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:384129ad0490e06bab2b98c1da9b488acb35bb11e2464c728376c6f55f0d45f3"},
{file = "websockets-13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:df5c0eff91f61b8205a6c9f7b255ff390cdb77b61c7b41f79ca10afcbb22b6cb"},
{file = "websockets-13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:02cc9bb1a887dac0e08bf657c5d00aa3fac0d03215d35a599130c2034ae6663a"},
{file = "websockets-13.0-cp312-cp312-win32.whl", hash = "sha256:d9726d2c9bd6aed8cb994d89b3910ca0079406edce3670886ec828a73e7bdd53"},
{file = "websockets-13.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0839f35322f7b038d8adcf679e2698c3a483688cc92e3bd15ee4fb06669e9a"},
{file = "websockets-13.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:da7e501e59857e8e3e9d10586139dc196b80445a591451ca9998aafba1af5278"},
{file = "websockets-13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a00e1e587c655749afb5b135d8d3edcfe84ec6db864201e40a882e64168610b3"},
{file = "websockets-13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a7fbf2a8fe7556a8f4e68cb3e736884af7bf93653e79f6219f17ebb75e97d8f0"},
{file = "websockets-13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ea9c9c7443a97ea4d84d3e4d42d0e8c4235834edae652993abcd2aff94affd7"},
{file = "websockets-13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35c2221b539b360203f3f9ad168e527bf16d903e385068ae842c186efb13d0ea"},
{file = "websockets-13.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:358d37c5c431dd050ffb06b4b075505aae3f4f795d7fff9794e5ed96ce99b998"},
{file = "websockets-13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:038e7a0f1bfafc7bf52915ab3506b7a03d1e06381e9f60440c856e8918138151"},
{file = "websockets-13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fd038bc9e2c134847f1e0ce3191797fad110756e690c2fdd9702ed34e7a43abb"},
{file = "websockets-13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93b8c2008f372379fb6e5d2b3f7c9ec32f7b80316543fd3a5ace6610c5cde1b0"},
{file = "websockets-13.0-cp313-cp313-win32.whl", hash = "sha256:851fd0afb3bc0b73f7c5b5858975d42769a5fdde5314f4ef2c106aec63100687"},
{file = "websockets-13.0-cp313-cp313-win_amd64.whl", hash = "sha256:7d14901fdcf212804970c30ab9ee8f3f0212e620c7ea93079d6534863444fb4e"},
{file = "websockets-13.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae7a519a56a714f64c3445cabde9fc2fc927e7eae44f413eae187cddd9e54178"},
{file = "websockets-13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5575031472ca87302aeb2ce2c2349f4c6ea978c86a9d1289bc5d16058ad4c10a"},
{file = "websockets-13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9895df6cd0bfe79d09bcd1dbdc03862846f26fbd93797153de954306620c1d00"},
{file = "websockets-13.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4de299c947a54fca9ce1c5fd4a08eb92ffce91961becb13bd9195f7c6e71b47"},
{file = "websockets-13.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05c25f7b849702950b6fd0e233989bb73a0d2bc83faa3b7233313ca395205f6d"},
{file = "websockets-13.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ede95125a30602b1691a4b1da88946bf27dae283cf30f22cd2cb8ca4b2e0d119"},
{file = "websockets-13.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:addf0a16e4983280efed272d8cb3b2e05f0051755372461e7d966b80a6554e16"},
{file = "websockets-13.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:06b3186e97bf9a33921fa60734d5ed90f2a9b407cce8d23c7333a0984049ef61"},
{file = "websockets-13.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:eae368cac85adc4c7dc3b0d5f84ffcca609d658db6447387300478e44db70796"},
{file = "websockets-13.0-cp38-cp38-win32.whl", hash = "sha256:337837ac788d955728b1ab01876d72b73da59819a3388e1c5e8e05c3999f1afa"},
{file = "websockets-13.0-cp38-cp38-win_amd64.whl", hash = "sha256:f66e00e42f25ca7e91076366303e11c82572ca87cc5aae51e6e9c094f315ab41"},
{file = "websockets-13.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:94c1c02721139fe9940b38d28fb15b4b782981d800d5f40f9966264fbf23dcc8"},
{file = "websockets-13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd4ba86513430513e2aa25a441bb538f6f83734dc368a2c5d18afdd39097aa33"},
{file = "websockets-13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a1ab8f0e0cadc5be5f3f9fa11a663957fecbf483d434762c8dfb8aa44948944a"},
{file = "websockets-13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3670def5d3dfd5af6f6e2b3b243ea8f1f72d8da1ef927322f0703f85c90d9603"},
{file = "websockets-13.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6058b6be92743358885ad6dcdecb378fde4a4c74d4dd16a089d07580c75a0e80"},
{file = "websockets-13.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:516062a0a8ef5ecbfa4acbaec14b199fc070577834f9fe3d40800a99f92523ca"},
{file = "websockets-13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:da7e918d82e7bdfc6f66d31febe1b2e28a1ca3387315f918de26f5e367f61572"},
{file = "websockets-13.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9cc7f35dcb49a4e32db82a849fcc0714c4d4acc9d2273aded2d61f87d7f660b7"},
{file = "websockets-13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f5737c53eb2c8ed8f64b50d3dafd3c1dae739f78aa495a288421ac1b3de82717"},
{file = "websockets-13.0-cp39-cp39-win32.whl", hash = "sha256:265e1f0d3f788ce8ef99dca591a1aec5263b26083ca0934467ad9a1d1181067c"},
{file = "websockets-13.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d70c89e3d3b347a7c4d3c33f8d323f0584c9ceb69b82c2ef8a174ca84ea3d4a"},
{file = "websockets-13.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:602cbd010d8c21c8475f1798b705bb18567eb189c533ab5ef568bc3033fdf417"},
{file = "websockets-13.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:bf8eb5dca4f484a60f5327b044e842e0d7f7cdbf02ea6dc4a4f811259f1f1f0b"},
{file = "websockets-13.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d795c1802d99a643bf689b277e8604c14b5af1bc0a31dade2cd7a678087212"},
{file = "websockets-13.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:788bc841d250beccff67a20a5a53a15657a60111ef9c0c0a97fbdd614fae0fe2"},
{file = "websockets-13.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7334752052532c156d28b8eaf3558137e115c7871ea82adff69b6d94a7bee273"},
{file = "websockets-13.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7a1963302947332c3039e3f66209ec73b1626f8a0191649e0713c391e9f5b0d"},
{file = "websockets-13.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e1cf4e1eb84b4fd74a47688e8b0940c89a04ad9f6937afa43d468e71128cd68"},
{file = "websockets-13.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:c026ee729c4ce55708a14b839ba35086dfae265fc12813b62d34ce33f4980c1c"},
{file = "websockets-13.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5f9d23fbbf96eefde836d9692670bfc89e2d159f456d499c5efcf6a6281c1af"},
{file = "websockets-13.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ad684cb7efce227d756bae3e8484f2e56aa128398753b54245efdfbd1108f2c"},
{file = "websockets-13.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1e10b3fbed7be4a59831d3a939900e50fcd34d93716e433d4193a4d0d1d335d"},
{file = "websockets-13.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d42a818e634f789350cd8fb413a3f5eec1cf0400a53d02062534c41519f5125c"},
{file = "websockets-13.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5ba5e9b332267d0f2c33ede390061850f1ac3ee6cd1bdcf4c5ea33ead971966"},
{file = "websockets-13.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f9af457ed593e35f467140d8b61d425495b127744a9d65d45a366f8678449a23"},
{file = "websockets-13.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcea3eb58c09c3a31cc83b45c06d5907f02ddaf10920aaa6443975310f699b95"},
{file = "websockets-13.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c210d1460dc8d326ffdef9703c2f83269b7539a1690ad11ae04162bc1878d33d"},
{file = "websockets-13.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b32f38bc81170fd56d0482d505b556e52bf9078b36819a8ba52624bd6667e39e"},
{file = "websockets-13.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:81a11a1ddd5320429db47c04d35119c3e674d215173d87aaeb06ae80f6e9031f"},
{file = "websockets-13.0-py3-none-any.whl", hash = "sha256:dbbac01e80aee253d44c4f098ab3cc17c822518519e869b284cfbb8cd16cc9de"},
{file = "websockets-13.0.tar.gz", hash = "sha256:b7bf950234a482b7461afdb2ec99eee3548ec4d53f418c7990bb79c620476602"},
{file = "websockets-13.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1841c9082a3ba4a05ea824cf6d99570a6a2d8849ef0db16e9c826acb28089e8f"},
{file = "websockets-13.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c5870b4a11b77e4caa3937142b650fbbc0914a3e07a0cf3131f35c0587489c1c"},
{file = "websockets-13.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f1d3d1f2eb79fe7b0fb02e599b2bf76a7619c79300fc55f0b5e2d382881d4f7f"},
{file = "websockets-13.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15c7d62ee071fa94a2fc52c2b472fed4af258d43f9030479d9c4a2de885fd543"},
{file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6724b554b70d6195ba19650fef5759ef11346f946c07dbbe390e039bcaa7cc3d"},
{file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a952fa2ae57a42ba7951e6b2605e08a24801a4931b5644dfc68939e041bc7f"},
{file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17118647c0ea14796364299e942c330d72acc4b248e07e639d34b75067b3cdd8"},
{file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64a11aae1de4c178fa653b07d90f2fb1a2ed31919a5ea2361a38760192e1858b"},
{file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0617fd0b1d14309c7eab6ba5deae8a7179959861846cbc5cb528a7531c249448"},
{file = "websockets-13.0.1-cp310-cp310-win32.whl", hash = "sha256:11f9976ecbc530248cf162e359a92f37b7b282de88d1d194f2167b5e7ad80ce3"},
{file = "websockets-13.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c3c493d0e5141ec055a7d6809a28ac2b88d5b878bb22df8c621ebe79a61123d0"},
{file = "websockets-13.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:699ba9dd6a926f82a277063603fc8d586b89f4cb128efc353b749b641fcddda7"},
{file = "websockets-13.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf2fae6d85e5dc384bf846f8243ddaa9197f3a1a70044f59399af001fd1f51d4"},
{file = "websockets-13.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:52aed6ef21a0f1a2a5e310fb5c42d7555e9c5855476bbd7173c3aa3d8a0302f2"},
{file = "websockets-13.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb2b9a318542153674c6e377eb8cb9ca0fc011c04475110d3477862f15d29f0"},
{file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5df891c86fe68b2c38da55b7aea7095beca105933c697d719f3f45f4220a5e0e"},
{file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac2d146ff30d9dd2fcf917e5d147db037a5c573f0446c564f16f1f94cf87462"},
{file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8ac5b46fd798bbbf2ac6620e0437c36a202b08e1f827832c4bf050da081b501"},
{file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46af561eba6f9b0848b2c9d2427086cabadf14e0abdd9fde9d72d447df268418"},
{file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b5a06d7f60bc2fc378a333978470dfc4e1415ee52f5f0fce4f7853eb10c1e9df"},
{file = "websockets-13.0.1-cp311-cp311-win32.whl", hash = "sha256:556e70e4f69be1082e6ef26dcb70efcd08d1850f5d6c5f4f2bcb4e397e68f01f"},
{file = "websockets-13.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:67494e95d6565bf395476e9d040037ff69c8b3fa356a886b21d8422ad86ae075"},
{file = "websockets-13.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f9c9e258e3d5efe199ec23903f5da0eeaad58cf6fccb3547b74fd4750e5ac47a"},
{file = "websockets-13.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6b41a1b3b561f1cba8321fb32987552a024a8f67f0d05f06fcf29f0090a1b956"},
{file = "websockets-13.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f73e676a46b0fe9426612ce8caeca54c9073191a77c3e9d5c94697aef99296af"},
{file = "websockets-13.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f613289f4a94142f914aafad6c6c87903de78eae1e140fa769a7385fb232fdf"},
{file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f52504023b1480d458adf496dc1c9e9811df4ba4752f0bc1f89ae92f4f07d0c"},
{file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:139add0f98206cb74109faf3611b7783ceafc928529c62b389917a037d4cfdf4"},
{file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:47236c13be337ef36546004ce8c5580f4b1150d9538b27bf8a5ad8edf23ccfab"},
{file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c44ca9ade59b2e376612df34e837013e2b273e6c92d7ed6636d0556b6f4db93d"},
{file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9bbc525f4be3e51b89b2a700f5746c2a6907d2e2ef4513a8daafc98198b92237"},
{file = "websockets-13.0.1-cp312-cp312-win32.whl", hash = "sha256:3624fd8664f2577cf8de996db3250662e259bfbc870dd8ebdcf5d7c6ac0b5185"},
{file = "websockets-13.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0513c727fb8adffa6d9bf4a4463b2bade0186cbd8c3604ae5540fae18a90cb99"},
{file = "websockets-13.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1ee4cc030a4bdab482a37462dbf3ffb7e09334d01dd37d1063be1136a0d825fa"},
{file = "websockets-13.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbb0b697cc0655719522406c059eae233abaa3243821cfdfab1215d02ac10231"},
{file = "websockets-13.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:acbebec8cb3d4df6e2488fbf34702cbc37fc39ac7abf9449392cefb3305562e9"},
{file = "websockets-13.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63848cdb6fcc0bf09d4a155464c46c64ffdb5807ede4fb251da2c2692559ce75"},
{file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:872afa52a9f4c414d6955c365b6588bc4401272c629ff8321a55f44e3f62b553"},
{file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e70fec7c54aad4d71eae8e8cab50525e899791fc389ec6f77b95312e4e9920"},
{file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e82db3756ccb66266504f5a3de05ac6b32f287faacff72462612120074103329"},
{file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e85f46ce287f5c52438bb3703d86162263afccf034a5ef13dbe4318e98d86e7"},
{file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3fea72e4e6edb983908f0db373ae0732b275628901d909c382aae3b592589f2"},
{file = "websockets-13.0.1-cp313-cp313-win32.whl", hash = "sha256:254ecf35572fca01a9f789a1d0f543898e222f7b69ecd7d5381d8d8047627bdb"},
{file = "websockets-13.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca48914cdd9f2ccd94deab5bcb5ac98025a5ddce98881e5cce762854a5de330b"},
{file = "websockets-13.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b74593e9acf18ea5469c3edaa6b27fa7ecf97b30e9dabd5a94c4c940637ab96e"},
{file = "websockets-13.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:132511bfd42e77d152c919147078460c88a795af16b50e42a0bd14f0ad71ddd2"},
{file = "websockets-13.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:165bedf13556f985a2aa064309baa01462aa79bf6112fbd068ae38993a0e1f1b"},
{file = "websockets-13.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e801ca2f448850685417d723ec70298feff3ce4ff687c6f20922c7474b4746ae"},
{file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30d3a1f041360f029765d8704eae606781e673e8918e6b2c792e0775de51352f"},
{file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67648f5e50231b5a7f6d83b32f9c525e319f0ddc841be0de64f24928cd75a603"},
{file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4f0426d51c8f0926a4879390f53c7f5a855e42d68df95fff6032c82c888b5f36"},
{file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ef48e4137e8799998a343706531e656fdec6797b80efd029117edacb74b0a10a"},
{file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:249aab278810bee585cd0d4de2f08cfd67eed4fc75bde623be163798ed4db2eb"},
{file = "websockets-13.0.1-cp38-cp38-win32.whl", hash = "sha256:06c0a667e466fcb56a0886d924b5f29a7f0886199102f0a0e1c60a02a3751cb4"},
{file = "websockets-13.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1f3cf6d6ec1142412d4535adabc6bd72a63f5f148c43fe559f06298bc21953c9"},
{file = "websockets-13.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1fa082ea38d5de51dd409434edc27c0dcbd5fed2b09b9be982deb6f0508d25bc"},
{file = "websockets-13.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a365bcb7be554e6e1f9f3ed64016e67e2fa03d7b027a33e436aecf194febb63"},
{file = "websockets-13.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10a0dc7242215d794fb1918f69c6bb235f1f627aaf19e77f05336d147fce7c37"},
{file = "websockets-13.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59197afd478545b1f73367620407b0083303569c5f2d043afe5363676f2697c9"},
{file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d20516990d8ad557b5abeb48127b8b779b0b7e6771a265fa3e91767596d7d97"},
{file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1a2e272d067030048e1fe41aa1ec8cfbbaabce733b3d634304fa2b19e5c897f"},
{file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad327ac80ba7ee61da85383ca8822ff808ab5ada0e4a030d66703cc025b021c4"},
{file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:518f90e6dd089d34eaade01101fd8a990921c3ba18ebbe9b0165b46ebff947f0"},
{file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68264802399aed6fe9652e89761031acc734fc4c653137a5911c2bfa995d6d6d"},
{file = "websockets-13.0.1-cp39-cp39-win32.whl", hash = "sha256:a5dc0c42ded1557cc7c3f0240b24129aefbad88af4f09346164349391dea8e58"},
{file = "websockets-13.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b448a0690ef43db5ef31b3a0d9aea79043882b4632cfc3eaab20105edecf6097"},
{file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:faef9ec6354fe4f9a2c0bbb52fb1ff852effc897e2a4501e25eb3a47cb0a4f89"},
{file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:03d3f9ba172e0a53e37fa4e636b86cc60c3ab2cfee4935e66ed1d7acaa4625ad"},
{file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d450f5a7a35662a9b91a64aefa852f0c0308ee256122f5218a42f1d13577d71e"},
{file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f55b36d17ac50aa8a171b771e15fbe1561217510c8768af3d546f56c7576cdc"},
{file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14b9c006cac63772b31abbcd3e3abb6228233eec966bf062e89e7fa7ae0b7333"},
{file = "websockets-13.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b79915a1179a91f6c5f04ece1e592e2e8a6bd245a0e45d12fd56b2b59e559a32"},
{file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f40de079779acbcdbb6ed4c65af9f018f8b77c5ec4e17a4b737c05c2db554491"},
{file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80e4ba642fc87fa532bac07e5ed7e19d56940b6af6a8c61d4429be48718a380f"},
{file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a02b0161c43cc9e0232711eff846569fad6ec836a7acab16b3cf97b2344c060"},
{file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aa74a45d4cdc028561a7d6ab3272c8b3018e23723100b12e58be9dfa5a24491"},
{file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00fd961943b6c10ee6f0b1130753e50ac5dcd906130dcd77b0003c3ab797d026"},
{file = "websockets-13.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d93572720d781331fb10d3da9ca1067817d84ad1e7c31466e9f5e59965618096"},
{file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:71e6e5a3a3728886caee9ab8752e8113670936a193284be9d6ad2176a137f376"},
{file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c4a6343e3b0714e80da0b0893543bf9a5b5fa71b846ae640e56e9abc6fbc4c83"},
{file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a678532018e435396e37422a95e3ab87f75028ac79570ad11f5bf23cd2a7d8c"},
{file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6716c087e4aa0b9260c4e579bb82e068f84faddb9bfba9906cb87726fa2e870"},
{file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e33505534f3f673270dd67f81e73550b11de5b538c56fe04435d63c02c3f26b5"},
{file = "websockets-13.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acab3539a027a85d568c2573291e864333ec9d912675107d6efceb7e2be5d980"},
{file = "websockets-13.0.1-py3-none-any.whl", hash = "sha256:b80f0c51681c517604152eb6a572f5a9378f877763231fddb883ba2f968e8817"},
{file = "websockets-13.0.1.tar.gz", hash = "sha256:4d6ece65099411cfd9a48d13701d7438d9c34f479046b34c50ff60bb8834e43e"},
]
[[package]]
@ -3119,20 +2975,24 @@ h11 = ">=0.9.0,<1"
[[package]]
name = "zipp"
version = "3.20.0"
version = "3.20.1"
description = "Backport of pathlib-compatible object wrapper for zip files"
optional = false
python-versions = ">=3.8"
files = [
{file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"},
{file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"},
{file = "zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064"},
{file = "zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"},
]
[package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
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"]
type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "4b609c46858e2a6766b9c9b759833a1366dbe7bb64b3332f339a9c9f713a870f"
content-hash = "0bd98b6baa48d41ec50bfa0903d89005129211f6e429d7b62504ee880f8ba523"

View File

@ -28,8 +28,8 @@ packages = [
[tool.poetry.dependencies]
python = "^3.8"
dill = ">=0.3.8,<0.4"
fastapi = ">=0.96.0,<0.111.0"
gunicorn = ">=20.1.0,<23.0"
fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
gunicorn = ">=20.1.0,<24.0"
jinja2 = ">=3.1.2,<4.0"
psutil = ">=5.9.4,<7.0"
pydantic = ">=1.10.2,<3.0"
@ -40,8 +40,6 @@ rich = ">=13.0.0,<14.0"
sqlmodel = ">=0.0.14,<0.1"
typer = ">=0.4.2,<1.0"
uvicorn = ">=0.20.0"
watchdog = ">=2.3.1,<5.0"
watchfiles = ">=0.19.0,<1.0"
starlette-admin = ">=0.11.0,<1.0"
alembic = ">=1.11.1,<2.0"
platformdirs = ">=3.10.0,<5.0"

View File

@ -1,4 +0,0 @@
*.db
*.py[cod]
.web
__pycache__/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,10 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Github" clip-path="url(#clip0_469_1929)">
<path id="Vector" d="M8.0004 0.587524C3.80139 0.587524 0.400391 3.98851 0.400391 8.1875C0.400391 11.5505 2.57589 14.391 5.59689 15.398C5.97689 15.4645 6.11939 15.2365 6.11939 15.037C6.11939 14.8565 6.10989 14.258 6.10989 13.6215C4.20039 13.973 3.70639 13.156 3.55439 12.7285C3.46889 12.51 3.09839 11.8355 2.77539 11.655C2.50939 11.5125 2.12939 11.161 2.76589 11.1515C3.36439 11.142 3.79189 11.7025 3.93439 11.9305C4.61839 13.08 5.71089 12.757 6.14789 12.5575C6.21439 12.0635 6.41388 11.731 6.6324 11.541C4.94139 11.351 3.17439 10.6955 3.17439 7.7885C3.17439 6.962 3.46889 6.27801 3.95339 5.74601C3.87739 5.55601 3.61139 4.77701 4.02939 3.73201C4.02939 3.73201 4.66589 3.53251 6.11939 4.51101C6.7274 4.34001 7.3734 4.25451 8.0194 4.25451C8.6654 4.25451 9.3114 4.34001 9.9194 4.51101C11.3729 3.52301 12.0094 3.73201 12.0094 3.73201C12.4274 4.77701 12.1614 5.55601 12.0854 5.74601C12.5699 6.27801 12.8644 6.9525 12.8644 7.7885C12.8644 10.705 11.0879 11.351 9.3969 11.541C9.6724 11.7785 9.9099 12.2345 9.9099 12.947C9.9099 13.9635 9.9004 14.7805 9.9004 15.037C9.9004 15.2365 10.0429 15.474 10.4229 15.398C13.5165 14.3536 15.5996 11.4527 15.6004 8.1875C15.6004 3.98851 12.1994 0.587524 8.0004 0.587524Z" fill="#494369"/>
</g>
<defs>
<clipPath id="clip0_469_1929">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,37 +0,0 @@
<svg width="67" height="14" viewBox="0 0 67 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="67" height="14" fill="#1E1E1E"/>
<g id="Nav Template &#62; Initial" clip-path="url(#clip0_0_1)">
<rect width="1440" height="1024" transform="translate(-16 -17)" fill="white"/>
<g id="Sidebar">
<g clip-path="url(#clip1_0_1)">
<path d="M-16 -17H264V1007H-16V-17Z" fill="white"/>
<g id="Header">
<path d="M-16 -17H264V31H-16V-17Z" fill="white"/>
<g id="Button">
<rect x="-4" y="-3" width="74.316" height="20" rx="6" fill="white"/>
<g id="Logo">
<g id="Reflex">
<path d="M0 13.6316V0.368408H10.6106V5.67369H7.95792V3.02105H2.65264V5.67369H7.95792V8.32633H2.65264V13.6316H0ZM7.95792 13.6316V8.32633H10.6106V13.6316H7.95792Z" fill="#110F1F"/>
<path d="M13.2632 13.6316V0.368408H21.2211V3.02105H15.9158V5.67369H21.2211V8.32633H15.9158V10.979H21.2211V13.6316H13.2632Z" fill="#110F1F"/>
<path d="M23.8738 13.6316V0.368408H31.8317V3.02105H26.5264V5.67369H31.8317V8.32633H26.5264V13.6316H23.8738Z" fill="#110F1F"/>
<path d="M34.4843 13.6316V0.368408H37.137V10.979H42.4422V13.6316H34.4843Z" fill="#110F1F"/>
<path d="M45.0949 13.6316V0.368408H53.0528V3.02105H47.7475V5.67369H53.0528V8.32633H47.7475V10.979H53.0528V13.6316H45.0949Z" fill="#110F1F"/>
<path d="M55.7054 5.67369V0.368408H58.3581V5.67369H55.7054ZM63.6634 5.67369V0.368408H66.316V5.67369H63.6634ZM58.3581 8.32633V5.67369H63.6634V8.32633H58.3581ZM55.7054 13.6316V8.32633H58.3581V13.6316H55.7054ZM63.6634 13.6316V8.32633H66.316V13.6316H63.6634Z" fill="#110F1F"/>
</g>
</g>
</g>
<path d="M264 30.5H-16V31.5H264V30.5Z" fill="#F4F3F6"/>
</g>
</g>
<path d="M263.5 -17V1007H264.5V-17H263.5Z" fill="#F4F3F6"/>
</g>
</g>
<defs>
<clipPath id="clip0_0_1">
<rect width="1440" height="1024" fill="white" transform="translate(-16 -17)"/>
</clipPath>
<clipPath id="clip1_0_1">
<path d="M-16 -17H264V1007H-16V-17Z" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,68 +0,0 @@
<svg width="80" height="78" viewBox="0 0 80 78" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_ddddi_449_2821)">
<path d="M13 11C13 6.58172 16.5817 3 21 3H59C63.4183 3 67 6.58172 67 11V49C67 52.3137 64.3137 55 61 55H19C15.6863 55 13 52.3137 13 49V11Z" fill="url(#paint0_radial_449_2821)"/>
<path d="M13 11C13 6.58172 16.5817 3 21 3H59C63.4183 3 67 6.58172 67 11V49C67 52.3137 64.3137 55 61 55H19C15.6863 55 13 52.3137 13 49V11Z" fill="url(#paint1_radial_449_2821)"/>
<g filter="url(#filter1_i_449_2821)">
<path d="M31 37.5C30.4477 37.5 30 37.0523 30 36.5V13.5001C30 12.9478 30.4477 12.5001 31 12.5001H49C49.5523 12.5001 50 12.9478 50 13.5001V21.5001C50 22.0524 49.5523 22.5001 49 22.5001H45V18.5001C45 17.9478 44.5523 17.5001 44 17.5001H36C35.4477 17.5001 35 17.9478 35 18.5001V21.5001C35 22.0524 35.4477 22.5001 36 22.5001H45V27.5001H36C35.4477 27.5001 35 27.9478 35 28.5001V36.5C35 37.0523 34.5523 37.5 34 37.5H31ZM46 37.5C45.4477 37.5 45 37.0523 45 36.5V27.5001H49C49.5523 27.5001 50 27.9478 50 28.5001V36.5C50 37.0523 49.5523 37.5 49 37.5H46Z" fill="url(#paint2_radial_449_2821)"/>
</g>
<path d="M13 11C13 6.58172 16.5817 3 21 3H59C63.4183 3 67 6.58172 67 11V49C67 52.3137 64.3137 55 61 55H19C15.6863 55 13 52.3137 13 49V11Z" stroke="#20117E" stroke-opacity="0.04"/>
</g>
<defs>
<filter id="filter0_ddddi_449_2821" x="0.5" y="0.5" width="79" height="77" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="4" operator="erode" in="SourceAlpha" result="effect1_dropShadow_449_2821"/>
<feOffset dy="10"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0784314 0 0 0 0 0.0705882 0 0 0 0 0.231373 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_449_2821"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="6" operator="erode" in="SourceAlpha" result="effect2_dropShadow_449_2821"/>
<feOffset dy="12"/>
<feGaussianBlur stdDeviation="3"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0784314 0 0 0 0 0.0705882 0 0 0 0 0.231373 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_449_2821" result="effect2_dropShadow_449_2821"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="4" operator="erode" in="SourceAlpha" result="effect3_dropShadow_449_2821"/>
<feOffset dy="10"/>
<feGaussianBlur stdDeviation="3"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.12549 0 0 0 0 0.0666667 0 0 0 0 0.494118 0 0 0 0.16 0"/>
<feBlend mode="normal" in2="effect2_dropShadow_449_2821" result="effect3_dropShadow_449_2821"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="1" operator="dilate" in="SourceAlpha" result="effect4_dropShadow_449_2821"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.12549 0 0 0 0 0.0666667 0 0 0 0 0.494118 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="effect3_dropShadow_449_2821" result="effect4_dropShadow_449_2821"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect4_dropShadow_449_2821" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="-8"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.678431 0 0 0 0 0.607843 0 0 0 0 0.972549 0 0 0 0.2 0"/>
<feBlend mode="normal" in2="shape" result="effect5_innerShadow_449_2821"/>
</filter>
<filter id="filter1_i_449_2821" x="30" y="12.5001" width="20" height="26.9999" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.12549 0 0 0 0 0.0666667 0 0 0 0 0.494118 0 0 0 0.32 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_449_2821"/>
</filter>
<radialGradient id="paint0_radial_449_2821" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 3) rotate(90) scale(52 54)">
<stop stop-color="white" stop-opacity="0.9"/>
<stop offset="1" stop-color="#4E3DB9" stop-opacity="0.24"/>
</radialGradient>
<radialGradient id="paint1_radial_449_2821" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 3) rotate(90) scale(52 54)">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F7F7F7"/>
</radialGradient>
<radialGradient id="paint2_radial_449_2821" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 12.5001) rotate(90) scale(24.9999 20)">
<stop stop-color="#F5F3FF"/>
<stop stop-color="white"/>
<stop offset="1" stop-color="#E1DDF4"/>
</radialGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,13 +0,0 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="PaneLeft" clip-path="url(#clip0_469_1942)">
<g id="Vector">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.80217 0.525009C7.34654 0.525009 6.97717 0.894373 6.97717 1.35001V10.65C6.97717 11.1056 7.34654 11.475 7.80217 11.475H10.6522C11.1078 11.475 11.4772 11.1056 11.4772 10.65V1.35001C11.4772 0.894373 11.1078 0.525009 10.6522 0.525009H7.80217ZM8.02717 10.425V1.57501H10.4272V10.425H8.02717Z" fill="#494369"/>
<path d="M3.78215 8.14502L2.16213 6.525H5.92717V5.475H2.16213L3.78215 3.85498L3.03969 3.11252L0.523438 5.62877V6.37123L3.03969 8.88748L3.78215 8.14502Z" fill="#494369"/>
</g>
</g>
<defs>
<clipPath id="clip0_469_1942">
<rect width="12" height="12" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 807 B

View File

@ -1 +0,0 @@
"""Base template for Reflex."""

View File

@ -1,127 +0,0 @@
"""Welcome to Reflex! This file outlines the steps to create a basic app."""
from typing import Callable
import reflex as rx
from .pages import chatapp_page, datatable_page, forms_page, graphing_page, home_page
from .sidebar import sidebar
from .state import State
from .styles import *
meta = [
{
"name": "viewport",
"content": "width=device-width, shrink-to-fit=no, initial-scale=1",
},
]
def template(main_content: Callable[[], rx.Component]) -> rx.Component:
"""The template for each page of the app.
Args:
main_content (Callable[[], rx.Component]): The main content of the page.
Returns:
rx.Component: The template for each page of the app.
"""
menu_button = rx.chakra.box(
rx.chakra.menu(
rx.chakra.menu_button(
rx.chakra.icon(
tag="hamburger",
size="4em",
color=text_color,
),
),
rx.chakra.menu_list(
rx.chakra.menu_item(rx.chakra.link("Home", href="/", width="100%")),
rx.chakra.menu_divider(),
rx.chakra.menu_item(
rx.chakra.link(
"About", href="https://github.com/reflex-dev", width="100%"
)
),
rx.chakra.menu_item(
rx.chakra.link(
"Contact", href="mailto:founders@reflex.dev", width="100%"
)
),
),
),
position="fixed",
right="1.5em",
top="1.5em",
z_index="500",
)
return rx.chakra.hstack(
sidebar(),
main_content(),
rx.chakra.spacer(),
menu_button,
align_items="flex-start",
transition="left 0.5s, width 0.5s",
position="relative",
left=rx.cond(State.sidebar_displayed, "0px", f"-{sidebar_width}"),
)
@rx.page("/", meta=meta)
@template
def home() -> rx.Component:
"""Home page.
Returns:
rx.Component: The home page.
"""
return home_page()
@rx.page("/forms", meta=meta)
@template
def forms() -> rx.Component:
"""Forms page.
Returns:
rx.Component: The settings page.
"""
return forms_page()
@rx.page("/graphing", meta=meta)
@template
def graphing() -> rx.Component:
"""Graphing page.
Returns:
rx.Component: The graphing page.
"""
return graphing_page()
@rx.page("/datatable", meta=meta)
@template
def datatable() -> rx.Component:
"""Data Table page.
Returns:
rx.Component: The chatapp page.
"""
return datatable_page()
@rx.page("/chatapp", meta=meta)
@template
def chatapp() -> rx.Component:
"""Chatapp page.
Returns:
rx.Component: The chatapp page.
"""
return chatapp_page()
# Create the app.
app = rx.App(style=base_style)

View File

@ -1,7 +0,0 @@
"""The pages of the app."""
from .chatapp import chatapp_page
from .datatable import datatable_page
from .forms import forms_page
from .graphing import graphing_page
from .home import home_page

View File

@ -1,31 +0,0 @@
"""The main Chat app."""
import reflex as rx
from ..styles import *
from ..webui import styles
from ..webui.components import chat, modal, navbar, sidebar
def chatapp_page() -> rx.Component:
"""The main app.
Returns:
The UI for the main app.
"""
return rx.chakra.box(
rx.chakra.vstack(
navbar(),
chat.chat(),
chat.action_bar(),
sidebar(),
modal(),
bg=styles.bg_dark_color,
color=styles.text_light_color,
min_h="100vh",
align_items="stretch",
spacing="0",
style=template_content_style,
),
style=template_page_style,
)

View File

@ -1,360 +0,0 @@
"""The settings page for the template."""
from typing import Any
import reflex as rx
from reflex.components.datadisplay.dataeditor import DataEditorTheme
from ..styles import *
from ..webui.state import State
class DataTableState(State):
"""Datatable state."""
cols: list[Any] = [
{"title": "Title", "type": "str"},
{
"title": "Name",
"type": "str",
"group": "Data",
"width": 300,
},
{
"title": "Birth",
"type": "str",
"group": "Data",
"width": 150,
},
{
"title": "Human",
"type": "bool",
"group": "Data",
"width": 80,
},
{
"title": "House",
"type": "str",
"group": "Data",
},
{
"title": "Wand",
"type": "str",
"group": "Data",
"width": 250,
},
{
"title": "Patronus",
"type": "str",
"group": "Data",
},
{
"title": "Blood status",
"type": "str",
"group": "Data",
"width": 200,
},
]
data = [
[
"1",
"Harry James Potter",
"31 July 1980",
True,
"Gryffindor",
"11' Holly phoenix feather",
"Stag",
"Half-blood",
],
[
"2",
"Ronald Bilius Weasley",
"1 March 1980",
True,
"Gryffindor",
"12' Ash unicorn tail hair",
"Jack Russell terrier",
"Pure-blood",
],
[
"3",
"Hermione Jean Granger",
"19 September, 1979",
True,
"Gryffindor",
"10¾' vine wood dragon heartstring",
"Otter",
"Muggle-born",
],
[
"4",
"Albus Percival Wulfric Brian Dumbledore",
"Late August 1881",
True,
"Gryffindor",
"15' Elder Thestral tail hair core",
"Phoenix",
"Half-blood",
],
[
"5",
"Rubeus Hagrid",
"6 December 1928",
False,
"Gryffindor",
"16' Oak unknown core",
"None",
"Part-Human (Half-giant)",
],
[
"6",
"Fred Weasley",
"1 April, 1978",
True,
"Gryffindor",
"Unknown",
"Unknown",
"Pure-blood",
],
[
"7",
"George Weasley",
"1 April, 1978",
True,
"Gryffindor",
"Unknown",
"Unknown",
"Pure-blood",
],
]
code_show = """rx.chakra.hstack(
rx.chakra.divider(orientation="vertical", height="100vh", border="solid black 1px"),
rx.chakra.vstack(
rx.chakra.box(
rx.data_editor(
columns=DataTableState.cols,
data=DataTableState.data,
draw_focus_ring=True,
row_height=50,
smooth_scroll_x=True,
smooth_scroll_y=True,
column_select="single",
# style
theme=DataEditorTheme(**darkTheme),
width="80vw",
height="80vh",
),
),
rx.chakra.spacer(),
height="100vh",
spacing="25",
),
)"""
state_show = """class DataTableState(State):
cols: list[Any] = [
{"title": "Title", "type": "str"},
{
"title": "Name",
"type": "str",
"group": "Data",
"width": 300,
},
{
"title": "Birth",
"type": "str",
"group": "Data",
"width": 150,
},
{
"title": "Human",
"type": "bool",
"group": "Data",
"width": 80,
},
{
"title": "House",
"type": "str",
"group": "Data",
},
{
"title": "Wand",
"type": "str",
"group": "Data",
"width": 250,
},
{
"title": "Patronus",
"type": "str",
"group": "Data",
},
{
"title": "Blood status",
"type": "str",
"group": "Data",
"width": 200,
},
]"""
data_show = """[
["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11' Holly phoenix feather", "Stag", "Half-blood"],
["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"],
["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"],
["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"],
["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"],
["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"],
["7", "George Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"],
]"""
darkTheme = {
"accent_color": "#8c96ff",
"accent_light": "rgba(202, 206, 255, 0.253)",
"text_dark": "#ffffff",
"text_medium": "#b8b8b8",
"text_light": "#a0a0a0",
"text_bubble": "#ffffff",
"bg_icon_header": "#b8b8b8",
"fg_icon_header": "#000000",
"text_header": "#a1a1a1",
"text_header_selected": "#000000",
"bg_cell": "#16161b",
"bg_cell_medium": "#202027",
"bg_header": "#212121",
"bg_header_has_focus": "#474747",
"bg_header_hovered": "#404040",
"bg_bubble": "#212121",
"bg_bubble_selected": "#000000",
"bg_search_result": "#423c24",
"border_color": "rgba(225,225,225,0.2)",
"drilldown_border": "rgba(225,225,225,0.4)",
"link_color": "#4F5DFF",
"header_font_style": "bold 14px",
"base_font_style": "13px",
"font_family": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif",
}
darkTheme_show = """darkTheme={
"accent_color": "#8c96ff",
"accent_light": "rgba(202, 206, 255, 0.253)",
"text_dark": "#ffffff",
"text_medium": "#b8b8b8",
"text_light": "#a0a0a0",
"text_bubble": "#ffffff",
"bg_icon_header": "#b8b8b8",
"fg_icon_header": "#000000",
"text_header": "#a1a1a1",
"text_header_selected": "#000000",
"bg_cell": "#16161b",
"bg_cell_medium": "#202027",
"bg_header": "#212121",
"bg_header_has_focus": "#474747",
"bg_header_hovered": "#404040",
"bg_bubble": "#212121",
"bg_bubble_selected": "#000000",
"bg_search_result": "#423c24",
"border_color": "rgba(225,225,225,0.2)",
"drilldown_border": "rgba(225,225,225,0.4)",
"link_color": "#4F5DFF",
"header_font_style": "bold 14px",
"base_font_style": "13px",
"font_family": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif",
}"""
def datatable_page() -> rx.Component:
"""The UI for the settings page.
Returns:
rx.Component: The UI for the settings page.
"""
return rx.chakra.box(
rx.chakra.vstack(
rx.chakra.heading(
"Data Table Demo",
font_size="3em",
),
rx.chakra.hstack(
rx.chakra.vstack(
rx.chakra.box(
rx.data_editor(
columns=DataTableState.cols,
data=DataTableState.data,
draw_focus_ring=True,
row_height=50,
smooth_scroll_x=True,
smooth_scroll_y=True,
column_select="single",
# style
theme=DataEditorTheme(**darkTheme),
width="80vw",
),
),
rx.chakra.spacer(),
spacing="25",
),
),
rx.chakra.tabs(
rx.chakra.tab_list(
rx.chakra.tab("Code", style=tab_style),
rx.chakra.tab("Data", style=tab_style),
rx.chakra.tab("State", style=tab_style),
rx.chakra.tab("Styling", style=tab_style),
padding_x=0,
),
rx.chakra.tab_panels(
rx.chakra.tab_panel(
rx.code_block(
code_show,
language="python",
show_line_numbers=True,
),
width="100%",
padding_x=0,
padding_y=".25em",
),
rx.chakra.tab_panel(
rx.code_block(
data_show,
language="python",
show_line_numbers=True,
),
width="100%",
padding_x=0,
padding_y=".25em",
),
rx.chakra.tab_panel(
rx.code_block(
state_show,
language="python",
show_line_numbers=True,
),
width="100%",
padding_x=0,
padding_y=".25em",
),
rx.chakra.tab_panel(
rx.code_block(
darkTheme_show,
language="python",
show_line_numbers=True,
),
width="100%",
padding_x=0,
padding_y=".25em",
),
width="100%",
),
variant="unstyled",
color_scheme="purple",
align="end",
width="100%",
padding_top=".5em",
),
style=template_content_style,
),
style=template_page_style,
)

View File

@ -1,257 +0,0 @@
"""The settings page for the template."""
import reflex as rx
from ..states.form_state import FormState, UploadState
from ..styles import *
forms_1_code = """rx.chakra.vstack(
rx.chakra.form(
rx.chakra.vstack(
rx.chakra.input(
placeholder="First Name",
id="first_name",
),
rx.chakra.input(
placeholder="Last Name", id="last_name"
),
rx.chakra.hstack(
rx.chakra.checkbox("Checked", id="check"),
rx.chakra.switch("Switched", id="switch"),
),
rx.chakra.button("Submit",
type_="submit",
bg="#ecfdf5",
color="#047857",
border_radius="lg",
),
),
on_submit=FormState.handle_submit,
),
rx.chakra.divider(),
rx.chakra.heading("Results"),
rx.chakra.text(FormState.form_data.to_string()),
width="100%",
)"""
color = "rgb(107,99,246)"
forms_1_state = """class FormState(rx.State):
form_data: dict = {}
def handle_submit(self, form_data: dict):
"Handle the form submit."
self.form_data = form_data"""
forms_2_code = """rx.chakra.vstack(
rx.upload(
rx.chakra.vstack(
rx.chakra.button(
"Select File",
color=color,
bg="white",
border=f"1px solid {color}",
),
rx.chakra.text(
"Drag and drop files here or click to select files"
),
),
border=f"1px dotted {color}",
padding="5em",
),
rx.chakra.hstack(rx.foreach(rx.selected_files, rx.chakra.text)),
rx.chakra.button(
"Upload",
on_click=lambda: UploadState.handle_upload(
rx.upload_files()
),
),
rx.chakra.button(
"Clear",
on_click=rx.clear_selected_files,
),
rx.foreach(
UploadState.img, lambda img: rx.chakra.image(src=img, width="20%", height="auto",)
),
padding="5em",
width="100%",
)"""
forms_2_state = """class UploadState(State):
"The app state."
# The images to show.
img: list[str]
async def handle_upload(
self, files: list[rx.UploadFile]
):
"Handle the upload of file(s).
Args:
files: The uploaded files.
"
for file in files:
upload_data = await file.read()
outfile = rx.get_asset_path(file.filename)
# Save the file.
with open(outfile, "wb") as file_object:
file_object.write(upload_data)
# Update the img var.
self.img.append(f"/{file.filename}")"""
def forms_page() -> rx.Component:
"""The UI for the settings page.
Returns:
rx.Component: The UI for the settings page.
"""
return rx.chakra.box(
rx.chakra.vstack(
rx.chakra.heading(
"Forms Demo",
font_size="3em",
),
rx.chakra.vstack(
rx.chakra.form(
rx.chakra.vstack(
rx.chakra.input(
placeholder="First Name",
id="first_name",
),
rx.chakra.input(placeholder="Last Name", id="last_name"),
rx.chakra.hstack(
rx.chakra.checkbox("Checked", id="check"),
rx.chakra.switch("Switched", id="switch"),
),
rx.chakra.button(
"Submit",
type_="submit",
bg="#ecfdf5",
color="#047857",
border_radius="lg",
),
),
on_submit=FormState.handle_submit,
),
rx.chakra.divider(),
rx.chakra.heading("Results"),
rx.chakra.text(FormState.form_data.to_string()),
width="100%",
),
rx.chakra.tabs(
rx.chakra.tab_list(
rx.chakra.tab("Code", style=tab_style),
rx.chakra.tab("State", style=tab_style),
padding_x=0,
),
rx.chakra.tab_panels(
rx.chakra.tab_panel(
rx.code_block(
forms_1_code,
language="python",
show_line_numbers=True,
),
width="100%",
padding_x=0,
padding_y=".25em",
),
rx.chakra.tab_panel(
rx.code_block(
forms_1_state,
language="python",
show_line_numbers=True,
),
width="100%",
padding_x=0,
padding_y=".25em",
),
width="100%",
),
variant="unstyled",
color_scheme="purple",
align="end",
width="100%",
padding_top=".5em",
),
rx.chakra.heading("Upload Example", font_size="3em"),
rx.chakra.text("Try uploading some images and see how they look."),
rx.chakra.vstack(
rx.upload(
rx.chakra.vstack(
rx.chakra.button(
"Select File",
color=color,
bg="white",
border=f"1px solid {color}",
),
rx.chakra.text(
"Drag and drop files here or click to select files"
),
),
border=f"1px dotted {color}",
padding="5em",
),
rx.chakra.hstack(rx.foreach(rx.selected_files, rx.chakra.text)),
rx.chakra.button(
"Upload",
on_click=lambda: UploadState.handle_upload(rx.upload_files()),
),
rx.chakra.button(
"Clear",
on_click=rx.clear_selected_files,
),
rx.foreach(
UploadState.img,
lambda img: rx.chakra.image(
src=img,
width="20%",
height="auto",
),
),
padding="5em",
width="100%",
),
rx.chakra.tabs(
rx.chakra.tab_list(
rx.chakra.tab("Code", style=tab_style),
rx.chakra.tab("State", style=tab_style),
padding_x=0,
),
rx.chakra.tab_panels(
rx.chakra.tab_panel(
rx.code_block(
forms_2_code,
language="python",
show_line_numbers=True,
),
width="100%",
padding_x=0,
padding_y=".25em",
),
rx.chakra.tab_panel(
rx.code_block(
forms_2_state,
language="python",
show_line_numbers=True,
),
width="100%",
padding_x=0,
padding_y=".25em",
),
width="100%",
),
variant="unstyled",
color_scheme="purple",
align="end",
width="100%",
padding_top=".5em",
),
style=template_content_style,
),
style=template_page_style,
)

View File

@ -1,253 +0,0 @@
"""The dashboard page for the template."""
import reflex as rx
from ..states.pie_state import PieChartState
from ..styles import *
data_1 = [
{"name": "Page A", "uv": 4000, "pv": 2400, "amt": 2400},
{"name": "Page B", "uv": 3000, "pv": 1398, "amt": 2210},
{"name": "Page C", "uv": 2000, "pv": 9800, "amt": 2290},
{"name": "Page D", "uv": 2780, "pv": 3908, "amt": 2000},
{"name": "Page E", "uv": 1890, "pv": 4800, "amt": 2181},
{"name": "Page F", "uv": 2390, "pv": 3800, "amt": 2500},
{"name": "Page G", "uv": 3490, "pv": 4300, "amt": 2100},
]
data_1_show = """[
{"name": "Page A", "uv": 4000, "pv": 2400, "amt": 2400},
{"name": "Page B", "uv": 3000, "pv": 1398, "amt": 2210},
{"name": "Page C", "uv": 2000, "pv": 9800, "amt": 2290},
{"name": "Page D", "uv": 2780, "pv": 3908, "amt": 2000},
{"name": "Page E", "uv": 1890, "pv": 4800, "amt": 2181},
{"name": "Page F", "uv": 2390, "pv": 3800, "amt": 2500},
{"name": "Page G", "uv": 3490, "pv": 4300, "amt": 2100},
]"""
graph_1_code = """rx.recharts.composed_chart(
rx.recharts.area(
data_key="uv", stroke="#8884d8", fill="#8884d8"
),
rx.recharts.bar(
data_key="amt", bar_size=20, fill="#413ea0"
),
rx.recharts.line(
data_key="pv", type_="monotone", stroke="#ff7300"
),
rx.recharts.x_axis(data_key="name"),
rx.recharts.y_axis(),
rx.recharts.cartesian_grid(stroke_dasharray="3 3"),
rx.recharts.graphing_tooltip(),
data=data,
)"""
graph_2_code = """rx.recharts.pie_chart(
rx.recharts.pie(
data=PieChartState.resources,
data_key="count",
name_key="type_",
cx="50%",
cy="50%",
start_angle=180,
end_angle=0,
fill="#8884d8",
label=True,
),
rx.recharts.graphing_tooltip(),
),
rx.chakra.vstack(
rx.foreach(
PieChartState.resource_types,
lambda type_, i: rx.chakra.hstack(
rx.chakra.button(
"-",
on_click=PieChartState.decrement(type_),
),
rx.chakra.text(
type_,
PieChartState.resources[i]["count"],
),
rx.chakra.button(
"+",
on_click=PieChartState.increment(type_),
),
),
),
)"""
graph_2_state = """class PieChartState(rx.State):
resources: list[dict[str, Any]] = [
dict(type_="🏆", count=1),
dict(type_="🪵", count=1),
dict(type_="🥑", count=1),
dict(type_="🧱", count=1),
]
@rx.cached_var
def resource_types(self) -> list[str]:
return [r["type_"] for r in self.resources]
def increment(self, type_: str):
for resource in self.resources:
if resource["type_"] == type_:
resource["count"] += 1
break
def decrement(self, type_: str):
for resource in self.resources:
if (
resource["type_"] == type_
and resource["count"] > 0
):
resource["count"] -= 1
break
"""
def graphing_page() -> rx.Component:
"""The UI for the dashboard page.
Returns:
rx.Component: The UI for the dashboard page.
"""
return rx.chakra.box(
rx.chakra.vstack(
rx.chakra.heading(
"Graphing Demo",
font_size="3em",
),
rx.chakra.heading(
"Composed Chart",
font_size="2em",
),
rx.chakra.stack(
rx.recharts.composed_chart(
rx.recharts.area(data_key="uv", stroke="#8884d8", fill="#8884d8"),
rx.recharts.bar(data_key="amt", bar_size=20, fill="#413ea0"),
rx.recharts.line(data_key="pv", type_="monotone", stroke="#ff7300"),
rx.recharts.x_axis(data_key="name"),
rx.recharts.y_axis(),
rx.recharts.cartesian_grid(stroke_dasharray="3 3"),
rx.recharts.graphing_tooltip(),
data=data_1,
# height="15em",
),
width="100%",
height="20em",
),
rx.chakra.tabs(
rx.chakra.tab_list(
rx.chakra.tab("Code", style=tab_style),
rx.chakra.tab("Data", style=tab_style),
padding_x=0,
),
rx.chakra.tab_panels(
rx.chakra.tab_panel(
rx.code_block(
graph_1_code,
language="python",
show_line_numbers=True,
),
width="100%",
padding_x=0,
padding_y=".25em",
),
rx.chakra.tab_panel(
rx.code_block(
data_1_show,
language="python",
show_line_numbers=True,
),
width="100%",
padding_x=0,
padding_y=".25em",
),
width="100%",
),
variant="unstyled",
color_scheme="purple",
align="end",
width="100%",
padding_top=".5em",
),
rx.chakra.heading("Interactive Example", font_size="2em"),
rx.chakra.hstack(
rx.recharts.pie_chart(
rx.recharts.pie(
data=PieChartState.resources,
data_key="count",
name_key="type_",
cx="50%",
cy="50%",
start_angle=180,
end_angle=0,
fill="#8884d8",
label=True,
),
rx.recharts.graphing_tooltip(),
),
rx.chakra.vstack(
rx.foreach(
PieChartState.resource_types,
lambda type_, i: rx.chakra.hstack(
rx.chakra.button(
"-",
on_click=PieChartState.decrement(type_),
),
rx.chakra.text(
type_,
PieChartState.resources[i]["count"],
),
rx.chakra.button(
"+",
on_click=PieChartState.increment(type_),
),
),
),
),
width="100%",
height="15em",
),
rx.chakra.tabs(
rx.chakra.tab_list(
rx.chakra.tab("Code", style=tab_style),
rx.chakra.tab("State", style=tab_style),
padding_x=0,
),
rx.chakra.tab_panels(
rx.chakra.tab_panel(
rx.code_block(
graph_2_code,
language="python",
show_line_numbers=True,
),
width="100%",
padding_x=0,
padding_y=".25em",
),
rx.chakra.tab_panel(
rx.code_block(
graph_2_state,
language="python",
show_line_numbers=True,
),
width="100%",
padding_x=0,
padding_y=".25em",
),
width="100%",
),
variant="unstyled",
color_scheme="purple",
align="end",
width="100%",
padding_top=".5em",
),
style=template_content_style,
min_h="100vh",
),
style=template_page_style,
min_h="100vh",
)

View File

@ -1,56 +0,0 @@
"""The home page of the app."""
import reflex as rx
from ..styles import *
def home_page() -> rx.Component:
"""The UI for the home page.
Returns:
rx.Component: The UI for the home page.
"""
return rx.chakra.box(
rx.chakra.vstack(
rx.chakra.heading(
"Welcome to Reflex! 👋",
font_size="3em",
),
rx.chakra.text(
"Reflex is an open-source app framework built specifically to allow you to build web apps in pure python. 👈 Select a demo from the sidebar to see some examples of what Reflex can do!",
),
rx.chakra.heading(
"Things to check out:",
font_size="2em",
),
rx.chakra.unordered_list(
rx.chakra.list_item(
"Take a look at ",
rx.chakra.link(
"reflex.dev",
href="https://reflex.dev",
color="rgb(107,99,246)",
),
),
rx.chakra.list_item(
"Check out our ",
rx.chakra.link(
"docs",
href="https://reflex.dev/docs/getting-started/introduction/",
color="rgb(107,99,246)",
),
),
rx.chakra.list_item(
"Ask a question in our ",
rx.chakra.link(
"community",
href="https://discord.gg/T5WSbC2YtQ",
color="rgb(107,99,246)",
),
),
),
style=template_content_style,
),
style=template_page_style,
)

View File

@ -1,178 +0,0 @@
"""Sidebar component for the app."""
import reflex as rx
from .state import State
from .styles import *
def sidebar_header() -> rx.Component:
"""Sidebar header.
Returns:
rx.Component: The sidebar header component.
"""
return rx.chakra.hstack(
rx.chakra.image(
src="/icon.svg",
height="2em",
),
rx.chakra.spacer(),
rx.chakra.link(
rx.chakra.center(
rx.chakra.image(
src="/github.svg",
height="3em",
padding="0.5em",
),
box_shadow=box_shadow,
bg="transparent",
border_radius=border_radius,
_hover={
"bg": accent_color,
},
),
href="https://github.com/reflex-dev/reflex",
),
width="100%",
border_bottom=border,
padding="1em",
)
def sidebar_footer() -> rx.Component:
"""Sidebar footer.
Returns:
rx.Component: The sidebar footer component.
"""
return rx.chakra.hstack(
rx.chakra.link(
rx.chakra.center(
rx.chakra.image(
src="/paneleft.svg",
height="2em",
padding="0.5em",
),
bg="transparent",
border_radius=border_radius,
**hover_accent_bg,
),
on_click=State.toggle_sidebar_displayed,
transform=rx.cond(~State.sidebar_displayed, "rotate(180deg)", ""),
transition="transform 0.5s, left 0.5s",
position="relative",
left=rx.cond(State.sidebar_displayed, "0px", "20.5em"),
**overlapping_button_style,
),
rx.chakra.spacer(),
rx.chakra.link(
rx.chakra.text(
"Docs",
),
href="https://reflex.dev/docs/getting-started/introduction/",
),
rx.chakra.link(
rx.chakra.text(
"Blog",
),
href="https://reflex.dev/blog/",
),
width="100%",
border_top=border,
padding="1em",
)
def sidebar_item(text: str, icon: str, url: str) -> rx.Component:
"""Sidebar item.
Args:
text (str): The text of the item.
icon (str): The icon of the item.
url (str): The URL of the item.
Returns:
rx.Component: The sidebar item component.
"""
return rx.chakra.link(
rx.chakra.hstack(
rx.chakra.image(
src=icon,
height="2.5em",
padding="0.5em",
),
rx.chakra.text(
text,
),
bg=rx.cond(
State.origin_url == f"/{text.lower()}/",
accent_color,
"transparent",
),
color=rx.cond(
State.origin_url == f"/{text.lower()}/",
accent_text_color,
text_color,
),
border_radius=border_radius,
box_shadow=box_shadow,
width="100%",
padding_x="1em",
),
href=url,
width="100%",
)
def sidebar() -> rx.Component:
"""Sidebar.
Returns:
rx.Component: The sidebar component.
"""
return rx.chakra.box(
rx.chakra.vstack(
sidebar_header(),
rx.chakra.vstack(
sidebar_item(
"Welcome",
"/github.svg",
"/",
),
sidebar_item(
"Graphing Demo",
"/github.svg",
"/graphing",
),
sidebar_item(
"Data Table Demo",
"/github.svg",
"/datatable",
),
sidebar_item(
"Forms Demo",
"/github.svg",
"/forms",
),
sidebar_item(
"Chat App Demo",
"/github.svg",
"/chatapp",
),
width="100%",
overflow_y="auto",
align_items="flex-start",
padding="1em",
),
rx.chakra.spacer(),
sidebar_footer(),
height="100dvh",
),
display=["none", "none", "block"],
min_width=sidebar_width,
height="100%",
position="sticky",
top="0px",
border_right=border,
)

View File

@ -1,22 +0,0 @@
"""Base state for the app."""
import reflex as rx
class State(rx.State):
"""State for the app."""
sidebar_displayed: bool = True
@rx.var
def origin_url(self) -> str:
"""Get the url of the current page.
Returns:
str: The url of the current page.
"""
return self.router_data.get("asPath", "")
def toggle_sidebar_displayed(self) -> None:
"""Toggle the sidebar displayed."""
self.sidebar_displayed = not self.sidebar_displayed

View File

@ -1,40 +0,0 @@
import reflex as rx
from ..state import State
class FormState(State):
"""Form state."""
form_data: dict = {}
def handle_submit(self, form_data: dict):
"""Handle the form submit.
Args:
form_data: The form data.
"""
self.form_data = form_data
class UploadState(State):
"""The app state."""
# The images to show.
img: list[str]
async def handle_upload(self, files: list[rx.UploadFile]):
"""Handle the upload of file(s).
Args:
files: The uploaded files.
"""
for file in files:
upload_data = await file.read()
outfile = rx.get_asset_path(file.filename)
# Save the file.
with open(outfile, "wb") as file_object:
file_object.write(upload_data)
# Update the img var.
self.img.append(f"/{file.filename}")

View File

@ -1,47 +0,0 @@
from typing import Any
import reflex as rx
from ..state import State
class PieChartState(State):
"""Pie Chart State."""
resources: list[dict[str, Any]] = [
dict(type_="🏆", count=1),
dict(type_="🪵", count=1),
dict(type_="🥑", count=1),
dict(type_="🧱", count=1),
]
@rx.cached_var
def resource_types(self) -> list[str]:
"""Get the resource types.
Returns:
The resource types.
"""
return [r["type_"] for r in self.resources]
def increment(self, type_: str):
"""Increment the count of a resource type.
Args:
type_: The type of resource to increment.
"""
for resource in self.resources:
if resource["type_"] == type_:
resource["count"] += 1
break
def decrement(self, type_: str):
"""Decrement the count of a resource type.
Args:
type_: The type of resource to decrement.
"""
for resource in self.resources:
if resource["type_"] == type_ and resource["count"] > 0:
resource["count"] -= 1
break

View File

@ -1,68 +0,0 @@
"""Styles for the app."""
import reflex as rx
from .state import State
border_radius = "0.375rem"
box_shadow = "0px 0px 0px 1px rgba(84, 82, 95, 0.14)"
border = "1px solid #F4F3F6"
text_color = "black"
accent_text_color = "#1A1060"
accent_color = "#F5EFFE"
hover_accent_color = {"_hover": {"color": accent_color}}
hover_accent_bg = {"_hover": {"bg": accent_color}}
content_width_vw = "90vw"
sidebar_width = "20em"
template_page_style = {
"padding_top": "5em",
"padding_x": "2em",
}
template_content_style = {
"width": rx.cond(
State.sidebar_displayed,
f"calc({content_width_vw} - {sidebar_width})",
content_width_vw,
),
"min-width": sidebar_width,
"align_items": "flex-start",
"box_shadow": box_shadow,
"border_radius": border_radius,
"padding": "1em",
"margin_bottom": "2em",
}
link_style = {
"color": text_color,
"text_decoration": "none",
**hover_accent_color,
}
overlapping_button_style = {
"background_color": "white",
"border": border,
"border_radius": border_radius,
}
base_style = {
rx.chakra.MenuButton: {
"width": "3em",
"height": "3em",
**overlapping_button_style,
},
rx.chakra.MenuItem: hover_accent_bg,
}
tab_style = {
"color": "#494369",
"font_weight": 600,
"_selected": {
"color": "#5646ED",
"bg": "#F5EFFE",
"padding_x": "0.5em",
"padding_y": "0.25em",
"border_radius": "8px",
},
}

View File

@ -1,4 +0,0 @@
from .loading_icon import loading_icon
from .modal import modal
from .navbar import navbar
from .sidebar import sidebar

View File

@ -1,118 +0,0 @@
import reflex as rx
from ...webui import styles
from ...webui.components import loading_icon
from ...webui.state import QA, State
def message(qa: QA) -> rx.Component:
"""A single question/answer message.
Args:
qa: The question/answer pair.
Returns:
A component displaying the question/answer pair.
"""
return rx.chakra.box(
rx.chakra.box(
rx.chakra.text(
qa.question,
bg=styles.border_color,
shadow=styles.shadow_light,
**styles.message_style,
),
text_align="right",
margin_top="1em",
),
rx.chakra.box(
rx.chakra.text(
qa.answer,
bg=styles.accent_color,
shadow=styles.shadow_light,
**styles.message_style,
),
text_align="left",
padding_top="1em",
),
width="100%",
)
def chat() -> rx.Component:
"""List all the messages in a single conversation.
Returns:
A component displaying all the messages in a single conversation.
"""
return rx.chakra.vstack(
rx.chakra.box(rx.foreach(State.chats[State.current_chat], message)),
py="8",
flex="1",
width="100%",
max_w="3xl",
padding_x="4",
align_self="center",
overflow="hidden",
padding_bottom="5em",
**styles.base_style[rx.chakra.Vstack],
)
def action_bar() -> rx.Component:
"""The action bar to send a new message.
Returns:
The action bar to send a new message.
"""
return rx.chakra.box(
rx.chakra.vstack(
rx.chakra.form(
rx.chakra.form_control(
rx.chakra.hstack(
rx.chakra.input(
placeholder="Type something...",
value=State.question,
on_change=State.set_question,
_placeholder={"color": "#fffa"},
_hover={"border_color": styles.accent_color},
style=styles.input_style,
),
rx.chakra.button(
rx.cond(
State.processing,
loading_icon(height="1em"),
rx.chakra.text("Send"),
),
type_="submit",
_hover={"bg": styles.accent_color},
style=styles.input_style,
),
**styles.base_style[rx.chakra.Hstack],
),
is_disabled=State.processing,
),
on_submit=State.process_question,
width="100%",
),
rx.chakra.text(
"ReflexGPT may return factually incorrect or misleading responses. Use discretion.",
font_size="xs",
color="#fff6",
text_align="center",
),
width="100%",
max_w="3xl",
mx="auto",
**styles.base_style[rx.chakra.Vstack],
),
position="sticky",
bottom="0",
left="0",
py="4",
backdrop_filter="auto",
backdrop_blur="lg",
border_top=f"1px solid {styles.border_color}",
align_items="stretch",
width="100%",
)

View File

@ -1,19 +0,0 @@
import reflex as rx
class LoadingIcon(rx.Component):
"""A custom loading icon component."""
library = "react-loading-icons"
tag = "SpinningCircles"
stroke: rx.Var[str]
stroke_opacity: rx.Var[str]
fill: rx.Var[str]
fill_opacity: rx.Var[str]
stroke_width: rx.Var[str]
speed: rx.Var[str]
height: rx.Var[str]
on_change: rx.EventHandler[lambda status: [status]]
loading_icon = LoadingIcon.create

View File

@ -1,56 +0,0 @@
import reflex as rx
from ...webui.state import State
def modal() -> rx.Component:
"""A modal to create a new chat.
Returns:
The modal component.
"""
return rx.chakra.modal(
rx.chakra.modal_overlay(
rx.chakra.modal_content(
rx.chakra.modal_header(
rx.chakra.hstack(
rx.chakra.text("Create new chat"),
rx.chakra.icon(
tag="close",
font_size="sm",
on_click=State.toggle_modal,
color="#fff8",
_hover={"color": "#fff"},
cursor="pointer",
),
align_items="center",
justify_content="space-between",
)
),
rx.chakra.modal_body(
rx.chakra.input(
placeholder="Type something...",
on_blur=State.set_new_chat_name,
bg="#222",
border_color="#fff3",
_placeholder={"color": "#fffa"},
),
),
rx.chakra.modal_footer(
rx.chakra.button(
"Create",
bg="#5535d4",
box_shadow="md",
px="4",
py="2",
h="auto",
_hover={"bg": "#4c2db3"},
on_click=State.create_chat,
),
),
bg="#222",
color="#fff",
),
),
is_open=State.modal_open,
)

View File

@ -1,70 +0,0 @@
import reflex as rx
from ...webui import styles
from ...webui.state import State
def navbar():
return rx.chakra.box(
rx.chakra.hstack(
rx.chakra.hstack(
rx.chakra.icon(
tag="hamburger",
mr=4,
on_click=State.toggle_drawer,
cursor="pointer",
),
rx.chakra.link(
rx.chakra.box(
rx.chakra.image(src="favicon.ico", width=30, height="auto"),
p="1",
border_radius="6",
bg="#F0F0F0",
mr="2",
),
href="/",
),
rx.chakra.breadcrumb(
rx.chakra.breadcrumb_item(
rx.chakra.heading("ReflexGPT", size="sm"),
),
rx.chakra.breadcrumb_item(
rx.chakra.text(
State.current_chat, size="sm", font_weight="normal"
),
),
),
),
rx.chakra.hstack(
rx.chakra.button(
"+ New chat",
bg=styles.accent_color,
px="4",
py="2",
h="auto",
on_click=State.toggle_modal,
),
rx.chakra.menu(
rx.chakra.menu_button(
rx.chakra.avatar(name="User", size="md"),
rx.chakra.box(),
),
rx.chakra.menu_list(
rx.chakra.menu_item("Help"),
rx.chakra.menu_divider(),
rx.chakra.menu_item("Settings"),
),
),
spacing="8",
),
justify="space-between",
),
bg=styles.bg_dark_color,
backdrop_filter="auto",
backdrop_blur="lg",
p="4",
border_bottom=f"1px solid {styles.border_color}",
position="sticky",
top="0",
z_index="100",
)

View File

@ -1,66 +0,0 @@
import reflex as rx
from ...webui import styles
from ...webui.state import State
def sidebar_chat(chat: str) -> rx.Component:
"""A sidebar chat item.
Args:
chat: The chat item.
Returns:
The sidebar chat item.
"""
return rx.chakra.hstack(
rx.chakra.box(
chat,
on_click=lambda: State.set_chat(chat),
style=styles.sidebar_style,
color=styles.icon_color,
flex="1",
),
rx.chakra.box(
rx.chakra.icon(
tag="delete",
style=styles.icon_style,
on_click=State.delete_chat,
),
style=styles.sidebar_style,
),
color=styles.text_light_color,
cursor="pointer",
)
def sidebar() -> rx.Component:
"""The sidebar component.
Returns:
The sidebar component.
"""
return rx.chakra.drawer(
rx.chakra.drawer_overlay(
rx.chakra.drawer_content(
rx.chakra.drawer_header(
rx.chakra.hstack(
rx.chakra.text("Chats"),
rx.chakra.icon(
tag="close",
on_click=State.toggle_drawer,
style=styles.icon_style,
),
)
),
rx.chakra.drawer_body(
rx.chakra.vstack(
rx.foreach(State.chat_titles, lambda chat: sidebar_chat(chat)),
align_items="stretch",
)
),
),
),
placement="left",
is_open=State.drawer_open,
)

View File

@ -1,146 +0,0 @@
import asyncio
import reflex as rx
from ..state import State
# openai.api_key = os.environ["OPENAI_API_KEY"]
# openai.api_base = os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1")
class QA(rx.Base):
"""A question and answer pair."""
question: str
answer: str
DEFAULT_CHATS = {
"Intros": [],
}
class State(State):
"""The app state."""
# A dict from the chat name to the list of questions and answers.
chats: dict[str, list[QA]] = DEFAULT_CHATS
# The current chat name.
current_chat = "Intros"
# The current question.
question: str
# Whether we are processing the question.
processing: bool = False
# The name of the new chat.
new_chat_name: str = ""
# Whether the drawer is open.
drawer_open: bool = False
# Whether the modal is open.
modal_open: bool = False
def create_chat(self):
"""Create a new chat."""
# Add the new chat to the list of chats.
self.current_chat = self.new_chat_name
self.chats[self.new_chat_name] = []
# Toggle the modal.
self.modal_open = False
def toggle_modal(self):
"""Toggle the new chat modal."""
self.modal_open = not self.modal_open
def toggle_drawer(self):
"""Toggle the drawer."""
self.drawer_open = not self.drawer_open
def delete_chat(self):
"""Delete the current chat."""
del self.chats[self.current_chat]
if len(self.chats) == 0:
self.chats = DEFAULT_CHATS
# set self.current_chat to the first chat.
self.current_chat = next(iter(self.chats))
self.toggle_drawer()
def set_chat(self, chat_name: str):
"""Set the name of the current chat.
Args:
chat_name: The name of the chat.
"""
self.current_chat = chat_name
self.toggle_drawer()
@rx.var
def chat_titles(self) -> list[str]:
"""Get the list of chat titles.
Returns:
The list of chat names.
"""
return [*self.chats]
async def process_question(self, form_data: dict[str, str]):
"""Get the response from the API.
Args:
form_data: A dict with the current question.
Yields:
The current question and the response.
"""
# Check if the question is empty
if self.question == "":
return
# Add the question to the list of questions.
qa = QA(question=self.question, answer="")
self.chats[self.current_chat].append(qa)
# Clear the input and start the processing.
self.processing = True
self.question = ""
yield
# # Build the messages.
# messages = [
# {"role": "system", "content": "You are a friendly chatbot named Reflex."}
# ]
# for qa in self.chats[self.current_chat]:
# messages.append({"role": "user", "content": qa.question})
# messages.append({"role": "assistant", "content": qa.answer})
# # Remove the last mock answer.
# messages = messages[:-1]
# Start a new session to answer the question.
# session = openai.ChatCompletion.create(
# model=os.getenv("OPENAI_MODEL", "gpt-3.5-turbo"),
# messages=messages,
# stream=True,
# )
# Stream the results, yielding after every word.
# for item in session:
answer = "I don't know! This Chatbot still needs to add in AI API keys!"
for i in range(len(answer)):
# Pause to show the streaming effect.
await asyncio.sleep(0.1)
# Add one letter at a time to the output.
# if hasattr(item.choices[0].delta, "content"):
# answer_text = item.choices[0].delta.content
self.chats[self.current_chat][-1].answer += answer[i]
self.chats = self.chats
yield
# Toggle the processing flag.
self.processing = False

View File

@ -1,88 +0,0 @@
import reflex as rx
bg_dark_color = "#111"
bg_medium_color = "#222"
border_color = "#fff3"
accennt_light = "#6649D8"
accent_color = "#5535d4"
accent_dark = "#4c2db3"
icon_color = "#fff8"
text_light_color = "#fff"
shadow_light = "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px;"
shadow = "rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px, rgba(10, 37, 64, 0.35) 0px -2px 6px 0px inset;"
message_style = dict(display="inline-block", p="4", border_radius="xl", max_w="30em")
input_style = dict(
bg=bg_medium_color,
border_color=border_color,
border_width="1px",
p="4",
)
icon_style = dict(
font_size="md",
color=icon_color,
_hover=dict(color=text_light_color),
cursor="pointer",
w="8",
)
sidebar_style = dict(
border="double 1px transparent;",
border_radius="10px;",
background_image=f"linear-gradient({bg_dark_color}, {bg_dark_color}), radial-gradient(circle at top left, {accent_color},{accent_dark});",
background_origin="border-box;",
background_clip="padding-box, border-box;",
p="2",
_hover=dict(
background_image=f"linear-gradient({bg_dark_color}, {bg_dark_color}), radial-gradient(circle at top left, {accent_color},{accennt_light});",
),
)
base_style = {
rx.chakra.Avatar: {
"shadow": shadow,
"color": text_light_color,
# "bg": border_color,
},
rx.chakra.Button: {
"shadow": shadow,
"color": text_light_color,
"_hover": {
"bg": accent_dark,
},
},
rx.chakra.Menu: {
"bg": bg_dark_color,
"border": f"red",
},
rx.chakra.MenuList: {
"bg": bg_dark_color,
"border": f"1.5px solid {bg_medium_color}",
},
rx.chakra.MenuDivider: {
"border": f"1px solid {bg_medium_color}",
},
rx.chakra.MenuItem: {
"bg": bg_dark_color,
"color": text_light_color,
},
rx.chakra.DrawerContent: {
"bg": bg_dark_color,
"color": text_light_color,
"opacity": "0.9",
},
rx.chakra.Hstack: {
"align_items": "center",
"justify_content": "space-between",
},
rx.chakra.Vstack: {
"align_items": "stretch",
"justify_content": "space-between",
},
}

View File

@ -140,6 +140,7 @@ RADIX_THEMES_COMPONENTS_MAPPING: dict = {
"components.radix.themes.components.radio_group": ["radio", "radio_group"],
"components.radix.themes.components.dropdown_menu": ["menu", "dropdown_menu"],
"components.radix.themes.components.separator": ["divider", "separator"],
"components.radix.themes.components.progress": ["progress"],
}
RADIX_THEMES_LAYOUT_MAPPING: dict = {
@ -205,7 +206,6 @@ RADIX_PRIMITIVES_MAPPING: dict = {
"components.radix.primitives.form": [
"form",
],
"components.radix.primitives.progress": ["progress"],
}
COMPONENTS_CORE_MAPPING: dict = {

View File

@ -71,7 +71,6 @@ from .components.plotly import plotly as plotly
from .components.radix.primitives.accordion import accordion as accordion
from .components.radix.primitives.drawer import drawer as drawer
from .components.radix.primitives.form import form as form
from .components.radix.primitives.progress import progress as progress
from .components.radix.themes.base import theme as theme
from .components.radix.themes.base import theme_panel as theme_panel
from .components.radix.themes.color_mode import color_mode as color_mode
@ -106,6 +105,7 @@ from .components.radix.themes.components.hover_card import hover_card as hover_c
from .components.radix.themes.components.icon_button import icon_button as icon_button
from .components.radix.themes.components.inset import inset as inset
from .components.radix.themes.components.popover import popover as popover
from .components.radix.themes.components.progress import progress as progress
from .components.radix.themes.components.radio_cards import radio_cards as radio_cards
from .components.radix.themes.components.radio_group import radio as radio
from .components.radix.themes.components.radio_group import radio_group as radio_group

View File

@ -33,7 +33,7 @@ from typing import (
from fastapi import FastAPI, HTTPException, Request, UploadFile
from fastapi.middleware import cors
from fastapi.responses import StreamingResponse
from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
from socketio import ASGIApp, AsyncNamespace, AsyncServer
@ -65,7 +65,7 @@ from reflex.components.core.upload import Upload, get_upload_dir
from reflex.components.radix import themes
from reflex.config import get_config
from reflex.event import Event, EventHandler, EventSpec, window_alert
from reflex.model import Model
from reflex.model import Model, get_db_status
from reflex.page import (
DECORATED_PAGES,
)
@ -377,6 +377,7 @@ class App(MiddlewareMixin, LifespanMixin, Base):
"""Add default api endpoints (ping)."""
# To test the server.
self.api.get(str(constants.Endpoint.PING))(ping)
self.api.get(str(constants.Endpoint.HEALTH))(health)
def _add_optional_endpoints(self):
"""Add optional api endpoints (_upload)."""
@ -1319,6 +1320,38 @@ async def ping() -> str:
return "pong"
async def health() -> JSONResponse:
"""Health check endpoint to assess the status of the database and Redis services.
Returns:
JSONResponse: A JSON object with the health status:
- "status" (bool): Overall health, True if all checks pass.
- "db" (bool or str): Database status - True, False, or "NA".
- "redis" (bool or str): Redis status - True, False, or "NA".
"""
health_status = {"status": True}
status_code = 200
db_status, redis_status = await asyncio.gather(
get_db_status(), prerequisites.get_redis_status()
)
health_status["db"] = db_status
if redis_status is None:
health_status["redis"] = False
else:
health_status["redis"] = redis_status
if not health_status["db"] or (
not health_status["redis"] and redis_status is not None
):
health_status["status"] = False
status_code = 503
return JSONResponse(content=health_status, status_code=status_code)
def upload(app: App):
"""Upload a file.

View File

@ -28,7 +28,7 @@ class Bare(Component):
"""
if isinstance(contents, ImmutableVar):
return cls(contents=contents)
if isinstance(contents, Var) and contents._get_all_var_data():
if isinstance(contents, Var):
contents = contents.to(str)
else:
contents = str(contents) if contents is not None else ""

View File

@ -45,7 +45,7 @@ from reflex.event import (
)
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.style import Style, format_as_emotion
from reflex.utils import console, format, imports, types
from reflex.utils import format, imports, types
from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports
from reflex.utils.serializers import serializer
from reflex.vars import BaseVar, ImmutableVarData, Var, VarData
@ -527,15 +527,7 @@ class Component(BaseComponent, ABC):
for v in value:
if isinstance(v, (EventHandler, EventSpec)):
# Call the event handler to get the event.
try:
event = call_event_handler(v, args_spec)
except ValueError as err:
raise ValueError(
f" {err} defined in the `{type(self).__name__}` component"
) from err
# Add the event to the chain.
events.append(event)
events.append(call_event_handler(v, args_spec))
elif isinstance(v, Callable):
# Call the lambda to get the event chain.
result = call_event_fn(v, args_spec)
@ -636,27 +628,6 @@ class Component(BaseComponent, ABC):
return _compile_component(self)
def _apply_theme(self, theme: Optional[Component]):
"""Apply the theme to this component.
Deprecated. Use add_style instead.
Args:
theme: The theme to apply.
"""
pass
def apply_theme(self, theme: Optional[Component]):
"""Apply a theme to the component and its children.
Args:
theme: The theme to apply.
"""
self._apply_theme(theme)
for child in self.children:
if isinstance(child, Component):
child.apply_theme(theme)
def _exclude_props(self) -> list[str]:
"""Props to exclude when adding the component props to the Tag.
@ -764,22 +735,6 @@ class Component(BaseComponent, ABC):
from reflex.components.base.fragment import Fragment
from reflex.utils.exceptions import ComponentTypeError
# Translate deprecated props to new names.
new_prop_names = [
prop for prop in cls.get_props() if prop in ["type", "min", "max"]
]
for prop in new_prop_names:
under_prop = f"{prop}_"
if under_prop in props:
console.deprecate(
f"Underscore suffix for prop `{under_prop}`",
reason=f"for consistency. Use `{prop}` instead.",
deprecation_version="0.4.0",
removal_version="0.6.0",
dedupe=False,
)
props[prop] = props.pop(under_prop)
# Filter out None props
props = {key: value for key, value in props.items() if value is not None}
@ -896,17 +851,6 @@ class Component(BaseComponent, ABC):
new_style.update(component_style)
style_vars.append(component_style._var_data)
# 3. User-defined style from `Component.style`.
# Apply theme for retro-compatibility with deprecated _apply_theme API
if type(self)._apply_theme != Component._apply_theme:
console.deprecate(
f"{self.__class__.__name__}._apply_theme",
reason="use add_style instead",
deprecation_version="0.5.0",
removal_version="0.6.0",
)
self._apply_theme(theme)
# 4. style dict and css props passed to the component instance.
new_style.update(self.style)
style_vars.append(self.style._var_data)

View File

@ -9,7 +9,7 @@ from reflex.components.component import BaseComponent, Component, MemoizationLea
from reflex.components.tags import CondTag, Tag
from reflex.constants import Dirs
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.ivars.number import TernaryOperator
from reflex.ivars.number import ternary_operation
from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode
from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars import Var, VarData
@ -163,11 +163,12 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | ImmutableVar:
c2 = create_var(c2)
# Create the conditional var.
return TernaryOperator.create(
condition=cond_var.to(bool), # type: ignore
if_true=c1,
if_false=c2,
_var_data=VarData(imports=_IS_TRUE_IMPORT),
return ternary_operation(
cond_var.bool()._replace( # type: ignore
merge_var_data=VarData(imports=_IS_TRUE_IMPORT),
), # type: ignore
c1,
c2,
)

View File

@ -11,7 +11,6 @@ from reflex.components.tags import IterTag
from reflex.constants import MemoizationMode
from reflex.ivars.base import ImmutableVar
from reflex.state import ComponentState
from reflex.utils import console
from reflex.vars import Var
@ -39,14 +38,12 @@ class Foreach(Component):
cls,
iterable: Var[Iterable] | Iterable,
render_fn: Callable,
**props,
) -> Foreach:
"""Create a foreach component.
Args:
iterable: The iterable to create components from.
render_fn: A function from the render args to the component.
**props: The attributes to pass to each child component (deprecated).
Returns:
The foreach component.
@ -55,13 +52,6 @@ class Foreach(Component):
ForeachVarError: If the iterable is of type Any.
TypeError: If the render function is a ComponentState.
"""
if props:
console.deprecate(
feature_name="Passing props to rx.foreach",
reason="it does not have the intended effect and may be confusing",
deprecation_version="0.5.0",
removal_version="0.6.0",
)
iterable = ImmutableVar.create_safe(iterable)
if iterable._var_type == Any:
raise ForeachVarError(

View File

@ -10,7 +10,6 @@ _SUBMODULES: set[str] = {"elements"}
_SUBMOD_ATTRS: dict[str, list[str]] = {
f"elements.{k}": v for k, v in elements._MAPPING.items()
}
_PYRIGHT_IGNORE_IMPORTS = elements._PYRIGHT_IGNORE_IMPORTS
__getattr__, __dir__, __all__ = lazy_loader.attach(
__name__,

View File

@ -3,7 +3,6 @@
# This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------
from . import elements
from .elements.forms import Button as Button
from .elements.forms import Fieldset as Fieldset
from .elements.forms import Form as Form
@ -88,36 +87,28 @@ from .elements.inline import u as u
from .elements.inline import wbr as wbr
from .elements.media import Area as Area
from .elements.media import Audio as Audio
from .elements.media import Defs as Defs
from .elements.media import Embed as Embed
from .elements.media import Iframe as Iframe
from .elements.media import Img as Img
from .elements.media import LinearGradient as LinearGradient
from .elements.media import Map as Map
from .elements.media import Object as Object
from .elements.media import Path as Path
from .elements.media import Picture as Picture
from .elements.media import Portal as Portal
from .elements.media import Source as Source
from .elements.media import Stop as Stop
from .elements.media import Svg as Svg
from .elements.media import Track as Track
from .elements.media import Video as Video
from .elements.media import area as area
from .elements.media import audio as audio
from .elements.media import defs as defs # type: ignore
from .elements.media import embed as embed
from .elements.media import iframe as iframe
from .elements.media import image as image
from .elements.media import img as img
from .elements.media import lineargradient as lineargradient # type: ignore
from .elements.media import map as map
from .elements.media import object as object
from .elements.media import path as path # type: ignore
from .elements.media import picture as picture
from .elements.media import portal as portal
from .elements.media import source as source
from .elements.media import stop as stop # type: ignore
from .elements.media import svg as svg
from .elements.media import track as track
from .elements.media import video as video
@ -231,5 +222,3 @@ from .elements.typography import ol as ol
from .elements.typography import p as p
from .elements.typography import pre as pre
from .elements.typography import ul as ul
_PYRIGHT_IGNORE_IMPORTS = elements._PYRIGHT_IGNORE_IMPORTS

View File

@ -65,11 +65,6 @@ _MAPPING = {
"portal",
"source",
"svg",
"defs",
"lineargradient",
"LinearGradient",
"stop",
"path",
],
"metadata": [
"base",
@ -130,13 +125,12 @@ _MAPPING = {
}
EXCLUDE = ["del_", "Del", "image", "lineargradient", "LinearGradient"]
EXCLUDE = ["del_", "Del", "image"]
for _, v in _MAPPING.items():
v.extend([mod.capitalize() for mod in v if mod not in EXCLUDE])
_SUBMOD_ATTRS: dict[str, list[str]] = _MAPPING
_PYRIGHT_IGNORE_IMPORTS = ["stop", "lineargradient", "path", "defs"]
__getattr__, __dir__, __all__ = lazy_loader.attach(
__name__,
submod_attrs=_SUBMOD_ATTRS,

View File

@ -87,36 +87,28 @@ from .inline import u as u
from .inline import wbr as wbr
from .media import Area as Area
from .media import Audio as Audio
from .media import Defs as Defs
from .media import Embed as Embed
from .media import Iframe as Iframe
from .media import Img as Img
from .media import LinearGradient as LinearGradient
from .media import Map as Map
from .media import Object as Object
from .media import Path as Path
from .media import Picture as Picture
from .media import Portal as Portal
from .media import Source as Source
from .media import Stop as Stop
from .media import Svg as Svg
from .media import Track as Track
from .media import Video as Video
from .media import area as area
from .media import audio as audio
from .media import defs as defs # type: ignore
from .media import embed as embed
from .media import iframe as iframe
from .media import image as image
from .media import img as img
from .media import lineargradient as lineargradient # type: ignore
from .media import map as map
from .media import object as object
from .media import path as path # type: ignore
from .media import picture as picture
from .media import portal as portal
from .media import source as source
from .media import stop as stop # type: ignore
from .media import svg as svg
from .media import track as track
from .media import video as video
@ -292,11 +284,6 @@ _MAPPING = {
"portal",
"source",
"svg",
"defs",
"lineargradient",
"LinearGradient",
"stop",
"path",
],
"metadata": ["base", "head", "link", "meta", "title", "style"],
"other": ["details", "dialog", "summary", "slot", "template", "math", "html"],
@ -348,7 +335,6 @@ _MAPPING = {
"Del",
],
}
EXCLUDE = ["del_", "Del", "image", "lineargradient", "LinearGradient"]
EXCLUDE = ["del_", "Del", "image"]
for _, v in _MAPPING.items():
v.extend([mod.capitalize() for mod in v if mod not in EXCLUDE])
_PYRIGHT_IGNORE_IMPORTS = ["stop", "lineargradient", "path", "defs"]

View File

@ -501,7 +501,7 @@ AUTO_HEIGHT_JS = """
const autoHeightOnInput = (e, is_enabled) => {
if (is_enabled) {
const el = e.target;
el.style.overflowY = "hidden";
el.style.overflowY = "scroll";
el.style.height = "auto";
el.style.height = (e.target.scrollHeight) + "px";
if (el.form && !el.form.data_resize_on_reset) {

View File

@ -1559,7 +1559,7 @@ class Select(BaseHTML):
"""
...
AUTO_HEIGHT_JS = '\nconst autoHeightOnInput = (e, is_enabled) => {\n if (is_enabled) {\n const el = e.target;\n el.style.overflowY = "hidden";\n el.style.height = "auto";\n el.style.height = (e.target.scrollHeight) + "px";\n if (el.form && !el.form.data_resize_on_reset) {\n el.form.addEventListener("reset", () => window.setTimeout(() => autoHeightOnInput(e, is_enabled), 0))\n el.form.data_resize_on_reset = true;\n }\n }\n}\n'
AUTO_HEIGHT_JS = '\nconst autoHeightOnInput = (e, is_enabled) => {\n if (is_enabled) {\n const el = e.target;\n el.style.overflowY = "scroll";\n el.style.height = "auto";\n el.style.height = (e.target.scrollHeight) + "px";\n if (el.form && !el.form.data_resize_on_reset) {\n el.form.addEventListener("reset", () => window.setTimeout(() => autoHeightOnInput(e, is_enabled), 0))\n el.form.data_resize_on_reset = true;\n }\n }\n}\n'
ENTER_KEY_SUBMIT_JS = "\nconst enterKeySubmitOnKeyDown = (e, is_enabled) => {\n if (is_enabled && e.which === 13 && !e.shiftKey) {\n e.preventDefault();\n if (!e.repeat) {\n if (e.target.form) {\n e.target.form.requestSubmit();\n }\n }\n }\n}\n"
class Textarea(BaseHTML):

View File

@ -4,7 +4,6 @@ from typing import Any, Union
from reflex import Component, ComponentNamespace
from reflex.constants.colors import Color
from reflex.utils import console
from reflex.vars import Var as Var
from .base import BaseHTML
@ -445,23 +444,3 @@ picture = Picture.create
portal = Portal.create
source = Source.create
svg = SVG()
def __getattr__(name: str):
if name in ("defs", "lineargradient", "stop", "path"):
console.deprecate(
f"`rx.el.{name}`",
reason=f"use `rx.el.svg.{'linear_gradient' if name =='lineargradient' else name}`",
deprecation_version="0.5.8",
removal_version="0.6.0",
)
return (
LinearGradient.create
if name == "lineargradient"
else globals()[name.capitalize()].create
)
try:
return globals()[name]
except KeyError:
raise AttributeError(f"module '{__name__} has no attribute '{name}'") from None

View File

@ -1,7 +1,7 @@
"""Lucide Icon component."""
from reflex.components.component import Component
from reflex.utils import console, format
from reflex.utils import format
from reflex.vars import Var
@ -36,19 +36,6 @@ class Icon(LucideIconComponent):
Returns:
The created component.
"""
def map_deprecated_icon_names_05(tag: str) -> str:
new_tag = RENAMED_ICONS_05.get(tag)
if new_tag is not None:
console.deprecate(
feature_name=f"icon {tag}",
reason=f"it was renamed upstream. Use {new_tag} instead.",
deprecation_version="0.4.6",
removal_version="0.6.0",
)
return new_tag
return tag
if children:
if len(children) == 1 and isinstance(children[0], str):
props["tag"] = children[0]
@ -62,8 +49,7 @@ class Icon(LucideIconComponent):
if (
not isinstance(props["tag"], str)
or map_deprecated_icon_names_05(format.to_snake_case(props["tag"]))
not in LUCIDE_ICON_LIST
or format.to_snake_case(props["tag"]) not in LUCIDE_ICON_LIST
):
raise ValueError(
f"Invalid icon tag: {props['tag']}. Please use one of the following: {', '.join(LUCIDE_ICON_LIST[0:25])}, ..."
@ -76,116 +62,6 @@ class Icon(LucideIconComponent):
return super().create(*children, **props)
RENAMED_ICONS_05 = {
"activity_square": "square_activity",
"alert_circle": "circle_alert",
"alert_octagon": "octagon_alert",
"alert_triangle": "triangle_alert",
"arrow_down_circle": "circle_arrow_down",
"arrow_down_left_from_circle": "circle_arrow_out_down_left",
"arrow_down_left_from_square": "square_arrow_out_down_left",
"arrow_down_left_square": "square_arrow_down_left",
"arrow_down_right_from_circle": "circle_arrow_out_down_right",
"arrow_down_right_from_square": "square_arrow_out_down_right",
"arrow_down_right_square": "square_arrow_down_right",
"arrow_down_square": "square_arrow_down",
"arrow_left_circle": "circle_arrow_left",
"arrow_left_square": "square_arrow_left",
"arrow_right_circle": "circle_arrow_right",
"arrow_right_square": "square_arrow_right",
"arrow_up_circle": "circle_arrow_up",
"arrow_up_left_from_circle": "circle_arrow_out_up_left",
"arrow_up_left_from_square": "square_arrow_out_up_left",
"arrow_up_left_square": "square_arrow_up_left",
"arrow_up_right_from_circle": "circle_arrow_out_up_right",
"arrow_up_right_from_square": "square_arrow_out_up_right",
"arrow_up_right_square": "square_arrow_up_right",
"arrow_up_square": "square_arrow_up",
"asterisk_square": "square_asterisk",
"check_circle": "circle_check_big",
"check_circle_2": "circle_check",
"check_square": "square_check_big",
"check_square_2": "square_check",
"chevron_down_circle": "circle_chevron_down",
"chevron_down_square": "square_chevron_down",
"chevron_left_circle": "circle_chevron_left",
"chevron_left_square": "square_chevron_left",
"chevron_right_circle": "circle_chevron_right",
"chevron_right_square": "square_chevron_right",
"chevron_up_circle": "circle_chevron_up",
"chevron_up_square": "square_chevron_up",
"code_2": "code_xml",
"code_square": "square_code",
"contact_2": "contact_round",
"divide_circle": "circle_divide",
"divide_square": "square_divide",
"dot_square": "square_dot",
"download_cloud": "cloud_download",
"equal_square": "square_equal",
"form_input": "rectangle_ellipsis",
"function_square": "square_function",
"gantt_chart_square": "square_gantt_chart",
"gauge_circle": "circle_gauge",
"globe_2": "earth",
"help_circle": "circle_help",
"helping_hand": "hand_helping",
"ice_cream": "ice_cream_cone",
"ice_cream_2": "ice_cream_bowl",
"indent": "indent_increase",
"kanban_square": "square_kanban",
"kanban_square_dashed": "square_dashed_kanban",
"laptop_2": "laptop_minimal",
"library_square": "square_library",
"loader_2": "loader_circle",
"m_square": "square_m",
"menu_square": "square_menu",
"mic_2": "mic_vocal",
"minus_circle": "circle_minus",
"minus_square": "square_minus",
"more_horizontal": "ellipsis",
"more_vertical": "ellipsis_vertical",
"mouse_pointer_square": "square_mouse_pointer",
"mouse_pointer_square_dashed": "square_dashed_mouse_pointer",
"outdent": "indent_decrease",
"palm_tree": "tree_palm",
"parking_circle": "circle_parking",
"parking_circle_off": "circle_parking_off",
"parking_square": "square_parking",
"parking_square_off": "square_parking_off",
"pause_circle": "circle_pause",
"pause_octagon": "octagon_pause",
"percent_circle": "circle_percent",
"percent_diamond": "diamond_percent",
"percent_square": "square_percent",
"pi_square": "square_pi",
"pilcrow_square": "square_pilcrow",
"play_circle": "circle_play",
"play_square": "square_play",
"plus_circle": "circle_plus",
"plus_square": "square_plus",
"power_circle": "circle_power",
"power_square": "square_power",
"school_2": "university",
"scissors_square": "square_scissors",
"scissors_square_dashed_bottom": "square_bottom_dashed_scissors",
"sigma_square": "square_sigma",
"slash_circle": "circle_slash",
"sliders": "sliders_vertical",
"split_square_horizontal": "square_split_horizontal",
"split_square_vertical": "square_split_vertical",
"stop_circle": "circle_stop",
"subtitles": "captions",
"test_tube_2": "test_tube_diagonal",
"unlock": "lock_open",
"unlock_keyhole": "lock_keyhole_open",
"upload_cloud": "cloud_upload",
"wallet_2": "wallet_minimal",
"wand_2": "wand_sparkles",
"x_circle": "circle_x",
"x_octagon": "octagon_x",
"x_square": "square_x",
}
LUCIDE_ICON_LIST = [
"a_arrow_down",
"a_arrow_up",

View File

@ -150,115 +150,6 @@ class Icon(LucideIconComponent):
"""
...
RENAMED_ICONS_05 = {
"activity_square": "square_activity",
"alert_circle": "circle_alert",
"alert_octagon": "octagon_alert",
"alert_triangle": "triangle_alert",
"arrow_down_circle": "circle_arrow_down",
"arrow_down_left_from_circle": "circle_arrow_out_down_left",
"arrow_down_left_from_square": "square_arrow_out_down_left",
"arrow_down_left_square": "square_arrow_down_left",
"arrow_down_right_from_circle": "circle_arrow_out_down_right",
"arrow_down_right_from_square": "square_arrow_out_down_right",
"arrow_down_right_square": "square_arrow_down_right",
"arrow_down_square": "square_arrow_down",
"arrow_left_circle": "circle_arrow_left",
"arrow_left_square": "square_arrow_left",
"arrow_right_circle": "circle_arrow_right",
"arrow_right_square": "square_arrow_right",
"arrow_up_circle": "circle_arrow_up",
"arrow_up_left_from_circle": "circle_arrow_out_up_left",
"arrow_up_left_from_square": "square_arrow_out_up_left",
"arrow_up_left_square": "square_arrow_up_left",
"arrow_up_right_from_circle": "circle_arrow_out_up_right",
"arrow_up_right_from_square": "square_arrow_out_up_right",
"arrow_up_right_square": "square_arrow_up_right",
"arrow_up_square": "square_arrow_up",
"asterisk_square": "square_asterisk",
"check_circle": "circle_check_big",
"check_circle_2": "circle_check",
"check_square": "square_check_big",
"check_square_2": "square_check",
"chevron_down_circle": "circle_chevron_down",
"chevron_down_square": "square_chevron_down",
"chevron_left_circle": "circle_chevron_left",
"chevron_left_square": "square_chevron_left",
"chevron_right_circle": "circle_chevron_right",
"chevron_right_square": "square_chevron_right",
"chevron_up_circle": "circle_chevron_up",
"chevron_up_square": "square_chevron_up",
"code_2": "code_xml",
"code_square": "square_code",
"contact_2": "contact_round",
"divide_circle": "circle_divide",
"divide_square": "square_divide",
"dot_square": "square_dot",
"download_cloud": "cloud_download",
"equal_square": "square_equal",
"form_input": "rectangle_ellipsis",
"function_square": "square_function",
"gantt_chart_square": "square_gantt_chart",
"gauge_circle": "circle_gauge",
"globe_2": "earth",
"help_circle": "circle_help",
"helping_hand": "hand_helping",
"ice_cream": "ice_cream_cone",
"ice_cream_2": "ice_cream_bowl",
"indent": "indent_increase",
"kanban_square": "square_kanban",
"kanban_square_dashed": "square_dashed_kanban",
"laptop_2": "laptop_minimal",
"library_square": "square_library",
"loader_2": "loader_circle",
"m_square": "square_m",
"menu_square": "square_menu",
"mic_2": "mic_vocal",
"minus_circle": "circle_minus",
"minus_square": "square_minus",
"more_horizontal": "ellipsis",
"more_vertical": "ellipsis_vertical",
"mouse_pointer_square": "square_mouse_pointer",
"mouse_pointer_square_dashed": "square_dashed_mouse_pointer",
"outdent": "indent_decrease",
"palm_tree": "tree_palm",
"parking_circle": "circle_parking",
"parking_circle_off": "circle_parking_off",
"parking_square": "square_parking",
"parking_square_off": "square_parking_off",
"pause_circle": "circle_pause",
"pause_octagon": "octagon_pause",
"percent_circle": "circle_percent",
"percent_diamond": "diamond_percent",
"percent_square": "square_percent",
"pi_square": "square_pi",
"pilcrow_square": "square_pilcrow",
"play_circle": "circle_play",
"play_square": "square_play",
"plus_circle": "circle_plus",
"plus_square": "square_plus",
"power_circle": "circle_power",
"power_square": "square_power",
"school_2": "university",
"scissors_square": "square_scissors",
"scissors_square_dashed_bottom": "square_bottom_dashed_scissors",
"sigma_square": "square_sigma",
"slash_circle": "circle_slash",
"sliders": "sliders_vertical",
"split_square_horizontal": "square_split_horizontal",
"split_square_vertical": "square_split_vertical",
"stop_circle": "circle_stop",
"subtitles": "captions",
"test_tube_2": "test_tube_diagonal",
"unlock": "lock_open",
"unlock_keyhole": "lock_keyhole_open",
"upload_cloud": "cloud_upload",
"wallet_2": "wallet_minimal",
"wand_2": "wand_sparkles",
"x_circle": "circle_x",
"x_octagon": "octagon_x",
"x_square": "square_x",
}
LUCIDE_ICON_LIST = [
"a_arrow_down",
"a_arrow_up",

View File

@ -8,7 +8,6 @@ from . import themes as themes
from .primitives.accordion import accordion as accordion
from .primitives.drawer import drawer as drawer
from .primitives.form import form as form
from .primitives.progress import progress as progress
from .themes.base import theme as theme
from .themes.base import theme_panel as theme_panel
from .themes.color_mode import color_mode as color_mode
@ -31,6 +30,7 @@ from .themes.components.hover_card import hover_card as hover_card
from .themes.components.icon_button import icon_button as icon_button
from .themes.components.inset import inset as inset
from .themes.components.popover import popover as popover
from .themes.components.progress import progress as progress
from .themes.components.radio_cards import radio_cards as radio_cards
from .themes.components.radio_group import radio as radio
from .themes.components.radio_group import radio_group as radio_group

View File

@ -6,4 +6,3 @@
from .accordion import accordion as accordion
from .drawer import drawer as drawer
from .form import form as form
from .progress import progress as progress

View File

@ -32,7 +32,6 @@ from reflex.style import (
set_color_mode,
toggle_color_mode,
)
from reflex.utils import console
from reflex.vars import Var
from .components.icon_button import IconButton
@ -100,7 +99,6 @@ class ColorModeIconButton(IconButton):
@classmethod
def create(
cls,
*children,
position: LiteralPosition | None = None,
allow_system: bool = False,
**props,
@ -108,7 +106,6 @@ class ColorModeIconButton(IconButton):
"""Create a icon button component that calls toggle_color_mode on click.
Args:
*children: The children of the component.
position: The position of the icon button. Follow document flow if None.
allow_system: Allow picking the "system" value for the color mode.
**props: The props to pass to the component.
@ -116,14 +113,6 @@ class ColorModeIconButton(IconButton):
Returns:
The button component.
"""
if children:
console.deprecate(
feature_name="passing children to color_mode.button",
reason=", use color_mode_cond and toggle_color_mode instead to build a custom color_mode component",
deprecation_version="0.5.0",
removal_version="0.6.0",
)
# position is used to set nice defaults for positioning the icon button
if isinstance(position, Var):
_set_var_default(props, position, "position", "fixed", position)

View File

@ -102,10 +102,6 @@ class ColorModeIconButton(IconButton):
def create( # type: ignore
cls,
*children,
position: Optional[
Literal["top-left", "top-right", "bottom-left", "bottom-right"]
] = None,
allow_system: Optional[bool] = False,
as_child: Optional[Union[Var[bool], bool]] = None,
size: Optional[
Union[
@ -280,7 +276,6 @@ class ColorModeIconButton(IconButton):
"""Create a icon button component that calls toggle_color_mode on click.
Args:
*children: The children of the component.
position: The position of the icon button. Follow document flow if None.
allow_system: Allow picking the "system" value for the color mode.
as_child: Change the default rendered element for the one passed as a child, merging their props and behavior.

View File

@ -22,6 +22,7 @@ from .hover_card import hover_card as hover_card
from .icon_button import icon_button as icon_button
from .inset import inset as inset
from .popover import popover as popover
from .progress import progress as progress
from .radio_cards import radio_cards as radio_cards
from .radio_group import radio as radio
from .radio_group import radio_group as radio_group

View File

@ -12,6 +12,7 @@ from reflex.components.radix.themes.typography.text import Text
from reflex.event import EventHandler
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.ivars.sequence import StringVar
from reflex.utils import types
from reflex.vars import Var
from ..base import (
@ -133,6 +134,9 @@ class HighLevelRadioGroup(RadixThemesComponent):
Returns:
The created radio group component.
Raises:
TypeError: If the type of items is invalid.
"""
direction = props.pop("direction", "column")
spacing = props.pop("spacing", "2")
@ -141,6 +145,16 @@ class HighLevelRadioGroup(RadixThemesComponent):
color_scheme = props.pop("color_scheme", None)
default_value = props.pop("default_value", "")
if (
not isinstance(items, (list, Var))
or isinstance(items, Var)
and not types._issubclass(items._var_type, list)
):
items_type = type(items) if not isinstance(items, Var) else items._var_type
raise TypeError(
f"The radio group component takes in a list, got {items_type} instead"
)
default_value = LiteralVar.create(default_value)
# convert only non-strings to json(JSON.stringify) so quotes are not rendered

View File

@ -419,6 +419,9 @@ class HighLevelRadioGroup(RadixThemesComponent):
Returns:
The created radio group component.
Raises:
TypeError: If the type of items is invalid.
"""
...
@ -588,6 +591,9 @@ class RadioGroup(ComponentNamespace):
Returns:
The created radio group component.
Raises:
TypeError: If the type of items is invalid.
"""
...

View File

@ -4,15 +4,11 @@ from __future__ import annotations
from typing import Literal, Union
from reflex.components.base.fragment import Fragment
from reflex.components.component import Component, ComponentNamespace
from reflex.components.core.breakpoints import Responsive
from reflex.components.core.debounce import DebounceInput
from reflex.components.el import elements
from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.style import Style, format_as_emotion
from reflex.utils import console
from reflex.vars import Var
from ..base import (
@ -107,80 +103,6 @@ class TextFieldRoot(elements.Div, RadixThemesComponent):
return DebounceInput.create(component)
return component
@classmethod
def create_root_deprecated(cls, *children, **props) -> Component:
"""Create a Fragment component (wrapper for deprecated name).
Copy the attributes that were previously defined on TextFieldRoot in 0.4.9 to
any child input elements (via custom_attrs).
Args:
*children: The children of the component.
**props: The properties of the component.
Returns:
The component.
"""
console.deprecate(
feature_name="rx.input.root",
reason="use rx.input without the .root suffix",
deprecation_version="0.5.0",
removal_version="0.6.0",
)
inputs = [
child
for child in children
if isinstance(child, (TextFieldRoot, DebounceInput))
]
if not inputs:
# Old-style where no explicit child input was provided
return cls.create(*children, **props)
slots = [child for child in children if isinstance(child, TextFieldSlot)]
carry_props = {
prop: props.pop(prop)
for prop in ["size", "variant", "color_scheme", "radius"]
if prop in props
}
template = cls.create(**props)
for child in inputs:
child.children.extend(slots)
custom_attrs = child.custom_attrs
custom_attrs.update(
{
prop: value
for prop, value in carry_props.items()
if prop not in custom_attrs and getattr(child, prop) is None
}
)
style = Style(template.style)
style.update(child.style)
child._get_style = lambda style=style: {
"css": LiteralVar.create(format_as_emotion(style))
}
for trigger in template.event_triggers:
if trigger not in child.event_triggers:
child.event_triggers[trigger] = template.event_triggers[trigger]
return Fragment.create(*inputs)
@classmethod
def create_input_deprecated(cls, *children, **props) -> Component:
"""Create a TextFieldRoot component (wrapper for deprecated name).
Args:
*children: The children of the component.
**props: The properties of the component.
Returns:
The component.
"""
console.deprecate(
feature_name="rx.input.input",
reason="use rx.input without the .input suffix",
deprecation_version="0.5.0",
removal_version="0.6.0",
)
return cls.create(*children, **props)
class TextFieldSlot(RadixThemesComponent):
"""Contains icons or buttons associated with an Input."""
@ -194,8 +116,6 @@ class TextFieldSlot(RadixThemesComponent):
class TextField(ComponentNamespace):
"""TextField components namespace."""
root = staticmethod(TextFieldRoot.create_root_deprecated)
input = staticmethod(TextFieldRoot.create_input_deprecated)
slot = staticmethod(TextFieldSlot.create)
__call__ = staticmethod(TextFieldRoot.create)

View File

@ -5,7 +5,7 @@
# ------------------------------------------------------
from typing import Any, Callable, Dict, Literal, Optional, Union, overload
from reflex.components.component import Component, ComponentNamespace
from reflex.components.component import ComponentNamespace
from reflex.components.core.breakpoints import Breakpoints
from reflex.components.el import elements
from reflex.event import EventHandler, EventSpec
@ -239,11 +239,6 @@ class TextFieldRoot(elements.Div, RadixThemesComponent):
"""
...
@classmethod
def create_root_deprecated(cls, *children, **props) -> Component: ...
@classmethod
def create_input_deprecated(cls, *children, **props) -> Component: ...
class TextFieldSlot(RadixThemesComponent):
@overload
@classmethod
@ -377,8 +372,6 @@ class TextFieldSlot(RadixThemesComponent):
...
class TextField(ComponentNamespace):
root = staticmethod(TextFieldRoot.create_root_deprecated)
input = staticmethod(TextFieldRoot.create_input_deprecated)
slot = staticmethod(TextFieldSlot.create)
@staticmethod

View File

@ -76,6 +76,9 @@ def _toast_callback_signature(toast: Var) -> list[Var]:
class ToastProps(PropsBase):
"""Props for the toast component."""
# Toast's title, renders above the description.
title: Optional[Union[str, Var]]
# Toast's description, renders underneath the title.
description: Optional[Union[str, Var]]
@ -290,7 +293,7 @@ class Toaster(Component):
return call_script(toast_action)
@staticmethod
def toast_info(message: str, **kwargs):
def toast_info(message: str = "", **kwargs):
"""Display an info toast message.
Args:
@ -303,7 +306,7 @@ class Toaster(Component):
return Toaster.send_toast(message, level="info", **kwargs)
@staticmethod
def toast_warning(message: str, **kwargs):
def toast_warning(message: str = "", **kwargs):
"""Display a warning toast message.
Args:
@ -316,7 +319,7 @@ class Toaster(Component):
return Toaster.send_toast(message, level="warning", **kwargs)
@staticmethod
def toast_error(message: str, **kwargs):
def toast_error(message: str = "", **kwargs):
"""Display an error toast message.
Args:
@ -329,7 +332,7 @@ class Toaster(Component):
return Toaster.send_toast(message, level="error", **kwargs)
@staticmethod
def toast_success(message: str, **kwargs):
def toast_success(message: str = "", **kwargs):
"""Display a success toast message.
Args:

View File

@ -32,6 +32,7 @@ class ToastAction(Base):
def serialize_action(action: ToastAction) -> dict: ...
class ToastProps(PropsBase):
title: Optional[Union[str, Var]]
description: Optional[Union[str, Var]]
close_button: Optional[bool]
invert: Optional[bool]
@ -65,13 +66,13 @@ class Toaster(Component):
message: str = "", level: str | None = None, **props
) -> EventSpec: ...
@staticmethod
def toast_info(message: str, **kwargs): ...
def toast_info(message: str = "", **kwargs): ...
@staticmethod
def toast_warning(message: str, **kwargs): ...
def toast_warning(message: str = "", **kwargs): ...
@staticmethod
def toast_error(message: str, **kwargs): ...
def toast_error(message: str = "", **kwargs): ...
@staticmethod
def toast_success(message: str, **kwargs): ...
def toast_success(message: str = "", **kwargs): ...
@staticmethod
def toast_dismiss(id: Var | str | None = None): ...
@overload

View File

@ -45,6 +45,8 @@ class Dirs(SimpleNamespace):
REFLEX_JSON = "reflex.json"
# The name of the postcss config file.
POSTCSS_JS = "postcss.config.js"
# The name of the states directory.
STATES = "states"
class Reflex(SimpleNamespace):
@ -103,7 +105,7 @@ class Templates(SimpleNamespace):
# The reflex.build backend host
REFLEX_BUILD_BACKEND = os.environ.get(
"REFLEX_BUILD_BACKEND", "https://rxh-prod-flexgen.fly.dev"
"REFLEX_BUILD_BACKEND", "https://flexgen-prod-flexgen.fly.dev"
)
# The URL to redirect to reflex.build

View File

@ -11,6 +11,7 @@ class Endpoint(Enum):
EVENT = "_event"
UPLOAD = "_upload"
AUTH_CODESPACE = "auth-codespace"
HEALTH = "_health"
def __str__(self) -> str:
"""Get the string representation of the endpoint.

View File

@ -3,6 +3,7 @@
from __future__ import annotations
import inspect
import types
import urllib.parse
from base64 import b64encode
from typing import (
@ -22,6 +23,7 @@ from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.ivars.function import FunctionStringVar, FunctionVar
from reflex.ivars.object import ObjectVar
from reflex.utils import format
from reflex.utils.exceptions import EventFnArgMismatch, EventHandlerArgMismatch
from reflex.utils.types import ArgsSpec
from reflex.vars import ImmutableVarData, Var
@ -831,7 +833,7 @@ def call_event_handler(
arg_spec: The lambda that define the argument(s) to pass to the event handler.
Raises:
ValueError: if number of arguments expected by event_handler doesn't match the spec.
EventHandlerArgMismatch: if number of arguments expected by event_handler doesn't match the spec.
Returns:
The event spec from calling the event handler.
@ -843,13 +845,16 @@ def call_event_handler(
return event_handler.add_args(*parsed_args)
args = inspect.getfullargspec(event_handler.fn).args
if len(args) == len(["self", *parsed_args]):
n_args = len(args) - 1 # subtract 1 for bound self arg
if n_args == len(parsed_args):
return event_handler(*parsed_args) # type: ignore
else:
source = inspect.getsource(arg_spec) # type: ignore
raise ValueError(
f"number of arguments in {event_handler.fn.__qualname__} "
f"doesn't match the definition of the event trigger '{source.strip().strip(',')}'"
raise EventHandlerArgMismatch(
"The number of arguments accepted by "
f"{event_handler.fn.__qualname__} ({n_args}) "
"does not match the arguments passed by the event trigger: "
f"{[str(v) for v in parsed_args]}\n"
"See https://reflex.dev/docs/events/event-arguments/"
)
@ -874,58 +879,60 @@ def parse_args_spec(arg_spec: ArgsSpec):
)
def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec] | Var:
def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var:
"""Call a function to a list of event specs.
The function should return a single EventSpec, a list of EventSpecs, or a
single Var. If the function takes in an arg, the arg will be passed to the
function. Otherwise, the function will be called with no args.
single Var. The function signature must match the passed arg_spec or
EventFnArgsMismatch will be raised.
Args:
fn: The function to call.
arg: The argument to pass to the function.
arg_spec: The argument spec for the event trigger.
Returns:
The event specs from calling the function or a Var.
Raises:
EventHandlerValueError: If the lambda has an invalid signature.
EventFnArgMismatch: If the function signature doesn't match the arg spec.
EventHandlerValueError: If the lambda returns an unusable value.
"""
# Import here to avoid circular imports.
from reflex.event import EventHandler, EventSpec
from reflex.utils.exceptions import EventHandlerValueError
# Get the args of the lambda.
args = inspect.getfullargspec(fn).args
# Check that fn signature matches arg_spec
fn_args = inspect.getfullargspec(fn).args
n_fn_args = len(fn_args)
if isinstance(fn, types.MethodType):
n_fn_args -= 1 # subtract 1 for bound self arg
parsed_args = parse_args_spec(arg_spec)
if len(parsed_args) != n_fn_args:
raise EventFnArgMismatch(
"The number of arguments accepted by "
f"{fn} ({n_fn_args}) "
"does not match the arguments passed by the event trigger: "
f"{[str(v) for v in parsed_args]}\n"
"See https://reflex.dev/docs/events/event-arguments/"
)
if isinstance(arg, ArgsSpec):
out = fn(*parse_args_spec(arg)) # type: ignore
else:
# Call the lambda.
if len(args) == 0:
out = fn()
elif len(args) == 1:
out = fn(arg)
else:
raise EventHandlerValueError(f"Lambda {fn} must have 0 or 1 arguments.")
# Call the function with the parsed args.
out = fn(*parsed_args)
# If the function returns a Var, assume it's an EventChain and render it directly.
if isinstance(out, Var):
return out
# Convert the output to a list.
if not isinstance(out, List):
if not isinstance(out, list):
out = [out]
# Convert any event specs to event specs.
events = []
for e in out:
# Convert handlers to event specs.
if isinstance(e, EventHandler):
if len(args) == 0:
e = e()
elif len(args) == 1:
e = e(arg) # type: ignore
# An un-called EventHandler gets all of the args of the event trigger.
e = call_event_handler(e, arg_spec)
# Make sure the event spec is valid.
if not isinstance(e, EventSpec):

View File

@ -32,18 +32,39 @@ class ExperimentalNamespace(SimpleNamespace):
Returns:
The toast namespace.
"""
if "toast" not in _EMITTED_PROMOTION_WARNINGS:
_EMITTED_PROMOTION_WARNINGS.add("toast")
warn(f"`rx._x.toast` was promoted to `rx.toast`.")
self.register_component_warning("toast")
return toast
@property
def progress(self):
"""Temporary property returning the toast namespace.
Remove this property when toast is fully promoted.
Returns:
The toast namespace.
"""
self.register_component_warning("progress")
return progress
@staticmethod
def register_component_warning(component_name: str):
"""Add component to emitted warnings and throw a warning if it
doesn't exist.
Args:
component_name: name of the component.
"""
if component_name not in _EMITTED_PROMOTION_WARNINGS:
_EMITTED_PROMOTION_WARNINGS.add(component_name)
warn(f"`rx._x.{component_name}` was promoted to `rx.{component_name}`.")
_x = ExperimentalNamespace(
asset=asset,
client_state=ClientStateVar.create,
hooks=hooks,
layout=layout,
progress=progress,
PropsBase=PropsBase,
run_in_thread=run_in_thread,
)

View File

@ -12,7 +12,6 @@ from .number import LiteralNumberVar as LiteralNumberVar
from .number import NumberVar as NumberVar
from .object import LiteralObjectVar as LiteralObjectVar
from .object import ObjectVar as ObjectVar
from .sequence import ArrayJoinOperation as ArrayJoinOperation
from .sequence import ArrayVar as ArrayVar
from .sequence import ConcatVarOperation as ConcatVarOperation
from .sequence import LiteralArrayVar as LiteralArrayVar

View File

@ -20,6 +20,7 @@ from typing import (
Generic,
List,
Literal,
NoReturn,
Optional,
Sequence,
Set,
@ -384,7 +385,15 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
return self.to(BooleanVar, output)
if issubclass(output, NumberVar):
if fixed_type is not None and not issubclass(fixed_type, (int, float)):
if fixed_type is not None:
if fixed_type is Union:
inner_types = get_args(base_type)
if not all(issubclass(t, (int, float)) for t in inner_types):
raise TypeError(
f"Unsupported type {var_type} for NumberVar. Must be int or float."
)
elif not issubclass(fixed_type, (int, float)):
raise TypeError(
f"Unsupported type {var_type} for NumberVar. Must be int or float."
)
@ -440,7 +449,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Raises:
TypeError: If the type is not supported for guessing.
"""
from .number import NumberVar
from .number import BooleanVar, NumberVar
from .object import ObjectVar
from .sequence import ArrayVar, StringVar
@ -454,11 +463,16 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
fixed_type = get_origin(var_type) or var_type
if fixed_type is Union:
inner_types = get_args(var_type)
if int in inner_types and float in inner_types:
return self.to(NumberVar, self._var_type)
return self
if not inspect.isclass(fixed_type):
raise TypeError(f"Unsupported type {var_type} for guess_type.")
if issubclass(fixed_type, bool):
return self.to(BooleanVar, self._var_type)
if issubclass(fixed_type, (int, float)):
return self.to(NumberVar, self._var_type)
if issubclass(fixed_type, dict):
@ -570,9 +584,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
BooleanVar: A BooleanVar object representing the result of the equality check.
"""
from .number import EqualOperation
from .number import equal_operation
return EqualOperation.create(self, other)
return equal_operation(self, other)
def __ne__(self, other: Var | Any) -> BooleanVar:
"""Check if the current object is not equal to the given object.
@ -583,9 +597,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
BooleanVar: A BooleanVar object representing the result of the comparison.
"""
from .number import EqualOperation
from .number import equal_operation
return ~EqualOperation.create(self, other)
return ~equal_operation(self, other)
def __gt__(self, other: Var | Any) -> BooleanVar:
"""Compare the current instance with another variable and return a BooleanVar representing the result of the greater than operation.
@ -596,9 +610,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
BooleanVar: A BooleanVar representing the result of the greater than operation.
"""
from .number import GreaterThanOperation
from .number import greater_than_operation
return GreaterThanOperation.create(self, other)
return greater_than_operation(self, other)
def __ge__(self, other: Var | Any) -> BooleanVar:
"""Check if the value of this variable is greater than or equal to the value of another variable or object.
@ -609,9 +623,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
BooleanVar: A BooleanVar object representing the result of the comparison.
"""
from .number import GreaterThanOrEqualOperation
from .number import greater_than_or_equal_operation
return GreaterThanOrEqualOperation.create(self, other)
return greater_than_or_equal_operation(self, other)
def __lt__(self, other: Var | Any) -> BooleanVar:
"""Compare the current instance with another variable using the less than (<) operator.
@ -622,9 +636,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A `BooleanVar` object representing the result of the comparison.
"""
from .number import LessThanOperation
from .number import less_than_operation
return LessThanOperation.create(self, other)
return less_than_operation(self, other)
def __le__(self, other: Var | Any) -> BooleanVar:
"""Compare if the current instance is less than or equal to the given value.
@ -635,9 +649,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A BooleanVar object representing the result of the comparison.
"""
from .number import LessThanOrEqualOperation
from .number import less_than_or_equal_operation
return LessThanOrEqualOperation.create(self, other)
return less_than_or_equal_operation(self, other)
def bool(self) -> BooleanVar:
"""Convert the var to a boolean.
@ -645,9 +659,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
The boolean var.
"""
from .number import ToBooleanVarOperation
from .number import boolify
return ToBooleanVarOperation.create(self)
return boolify(self)
def __and__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on the current instance and another variable.
@ -658,7 +672,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A `BooleanVar` object representing the result of the logical AND operation.
"""
return AndOperation.create(self, other)
return and_operation(self, other)
def __rand__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on the current instance and another variable.
@ -669,7 +683,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A `BooleanVar` object representing the result of the logical AND operation.
"""
return AndOperation.create(other, self)
return and_operation(other, self)
def __or__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on the current instance and another variable.
@ -680,7 +694,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A `BooleanVar` object representing the result of the logical OR operation.
"""
return OrOperation.create(self, other)
return or_operation(self, other)
def __ror__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on the current instance and another variable.
@ -691,7 +705,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A `BooleanVar` object representing the result of the logical OR operation.
"""
return OrOperation.create(other, self)
return or_operation(other, self)
def __invert__(self) -> BooleanVar:
"""Perform a logical NOT operation on the current instance.
@ -699,9 +713,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A `BooleanVar` object representing the result of the logical NOT operation.
"""
from .number import BooleanNotOperation
return BooleanNotOperation.create(self.bool())
return ~self.bool()
def to_string(self) -> ImmutableVar:
"""Convert the var to a string.
@ -926,53 +938,93 @@ class LiteralVar(ImmutableVar):
P = ParamSpec("P")
T = TypeVar("T", bound=ImmutableVar)
T = TypeVar("T")
def var_operation(*, output: Type[T]) -> Callable[[Callable[P, str]], Callable[P, T]]:
# NoReturn is used to match CustomVarOperationReturn with no type hint.
@overload
def var_operation(
func: Callable[P, CustomVarOperationReturn[NoReturn]],
) -> Callable[P, ImmutableVar]: ...
@overload
def var_operation(
func: Callable[P, CustomVarOperationReturn[bool]],
) -> Callable[P, BooleanVar]: ...
NUMBER_T = TypeVar("NUMBER_T", int, float, Union[int, float])
@overload
def var_operation(
func: Callable[P, CustomVarOperationReturn[NUMBER_T]],
) -> Callable[P, NumberVar[NUMBER_T]]: ...
@overload
def var_operation(
func: Callable[P, CustomVarOperationReturn[str]],
) -> Callable[P, StringVar]: ...
LIST_T = TypeVar("LIST_T", bound=Union[List[Any], Tuple, Set])
@overload
def var_operation(
func: Callable[P, CustomVarOperationReturn[LIST_T]],
) -> Callable[P, ArrayVar[LIST_T]]: ...
OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Dict)
@overload
def var_operation(
func: Callable[P, CustomVarOperationReturn[OBJECT_TYPE]],
) -> Callable[P, ObjectVar[OBJECT_TYPE]]: ...
def var_operation(
func: Callable[P, CustomVarOperationReturn[T]],
) -> Callable[P, ImmutableVar[T]]:
"""Decorator for creating a var operation.
Example:
```python
@var_operation(output=NumberVar)
@var_operation
def add(a: NumberVar, b: NumberVar):
return f"({a} + {b})"
return custom_var_operation(f"{a} + {b}")
```
Args:
output: The output type of the operation.
func: The function to decorate.
Returns:
The decorator.
The decorated function.
"""
def decorator(func: Callable[P, str], output=output):
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
args_vars = [
LiteralVar.create(arg) if not isinstance(arg, Var) else arg
for arg in args
]
def wrapper(*args: P.args, **kwargs: P.kwargs) -> ImmutableVar[T]:
func_args = list(inspect.signature(func).parameters)
args_vars = {
func_args[i]: (LiteralVar.create(arg) if not isinstance(arg, Var) else arg)
for i, arg in enumerate(args)
}
kwargs_vars = {
key: LiteralVar.create(value) if not isinstance(value, Var) else value
for key, value in kwargs.items()
}
return output(
_var_name=func(*args_vars, **kwargs_vars), # type: ignore
_var_data=VarData.merge(
*[arg._get_all_var_data() for arg in args if isinstance(arg, Var)],
*[
arg._get_all_var_data()
for arg in kwargs.values()
if isinstance(arg, Var)
],
),
)
return CustomVarOperation.create(
args=tuple(list(args_vars.items()) + list(kwargs_vars.items())),
return_var=func(*args_vars.values(), **kwargs_vars), # type: ignore
).guess_type()
return wrapper
return decorator
def unionize(*args: Type) -> Type:
"""Unionize the types.
@ -1100,113 +1152,63 @@ class CachedVarOperation:
)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class AndOperation(CachedVarOperation, ImmutableVar):
"""Class for the logical AND operation."""
# The first var.
_var1: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
# The second var.
_var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""Get the cached var name.
Returns:
The cached var name.
"""
return f"({str(self._var1)} && {str(self._var2)})"
def __hash__(self) -> int:
"""Calculates the hash value of the object.
Returns:
int: The hash value of the object.
"""
return hash((self.__class__.__name__, self._var1, self._var2))
@classmethod
def create(
cls, var1: Var | Any, var2: Var | Any, _var_data: VarData | None = None
) -> AndOperation:
"""Create an AndOperation.
def and_operation(a: Var | Any, b: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on two variables.
Args:
var1: The first var.
var2: The second var.
_var_data: Additional hooks and imports associated with the Var.
a: The first variable.
b: The second variable.
Returns:
The AndOperation.
The result of the logical AND operation.
"""
var1, var2 = map(LiteralVar.create, (var1, var2))
return AndOperation(
_var_name="",
_var_type=unionize(var1._var_type, var2._var_type),
_var_data=ImmutableVarData.merge(_var_data),
_var1=var1,
_var2=var2,
return _and_operation(a, b) # type: ignore
@var_operation
def _and_operation(a: ImmutableVar, b: ImmutableVar):
"""Perform a logical AND operation on two variables.
Args:
a: The first variable.
b: The second variable.
Returns:
The result of the logical AND operation.
"""
return var_operation_return(
js_expression=f"({a} && {b})",
var_type=unionize(a._var_type, b._var_type),
)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class OrOperation(CachedVarOperation, ImmutableVar):
"""Class for the logical OR operation."""
# The first var.
_var1: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
# The second var.
_var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""Get the cached var name.
Returns:
The cached var name.
"""
return f"({str(self._var1)} || {str(self._var2)})"
def __hash__(self) -> int:
"""Calculates the hash value for the object.
Returns:
int: The hash value of the object.
"""
return hash((self.__class__.__name__, self._var1, self._var2))
@classmethod
def create(
cls, var1: Var | Any, var2: Var | Any, _var_data: VarData | None = None
) -> OrOperation:
"""Create an OrOperation.
def or_operation(a: Var | Any, b: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on two variables.
Args:
var1: The first var.
var2: The second var.
_var_data: Additional hooks and imports associated with the Var.
a: The first variable.
b: The second variable.
Returns:
The OrOperation.
The result of the logical OR operation.
"""
var1, var2 = map(LiteralVar.create, (var1, var2))
return OrOperation(
_var_name="",
_var_type=unionize(var1._var_type, var2._var_type),
_var_data=ImmutableVarData.merge(_var_data),
_var1=var1,
_var2=var2,
return _or_operation(a, b) # type: ignore
@var_operation
def _or_operation(a: ImmutableVar, b: ImmutableVar):
"""Perform a logical OR operation on two variables.
Args:
a: The first variable.
b: The second variable.
Returns:
The result of the logical OR operation.
"""
return var_operation_return(
js_expression=f"({a} || {b})",
var_type=unionize(a._var_type, b._var_type),
)
@ -1797,3 +1799,114 @@ def immutable_computed_var(
)
return wrapper
RETURN = TypeVar("RETURN")
class CustomVarOperationReturn(ImmutableVar[RETURN]):
"""Base class for custom var operations."""
@classmethod
def create(
cls,
js_expression: str,
_var_type: Type[RETURN] | None = None,
_var_data: VarData | None = None,
) -> CustomVarOperationReturn[RETURN]:
"""Create a CustomVarOperation.
Args:
js_expression: The JavaScript expression to evaluate.
_var_type: The type of the var.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The CustomVarOperation.
"""
return CustomVarOperationReturn(
_var_name=js_expression,
_var_type=_var_type or Any,
_var_data=ImmutableVarData.merge(_var_data),
)
def var_operation_return(
js_expression: str,
var_type: Type[RETURN] | None = None,
) -> CustomVarOperationReturn[RETURN]:
"""Shortcut for creating a CustomVarOperationReturn.
Args:
js_expression: The JavaScript expression to evaluate.
var_type: The type of the var.
Returns:
The CustomVarOperationReturn.
"""
return CustomVarOperationReturn.create(js_expression, var_type)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class CustomVarOperation(CachedVarOperation, ImmutableVar[T]):
"""Base class for custom var operations."""
_args: Tuple[Tuple[str, Var], ...] = dataclasses.field(default_factory=tuple)
_return: CustomVarOperationReturn[T] = dataclasses.field(
default_factory=lambda: CustomVarOperationReturn.create("")
)
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""Get the cached var name.
Returns:
The cached var name.
"""
return str(self._return)
@cached_property_no_lock
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
"""Get the cached VarData.
Returns:
The cached VarData.
"""
return ImmutableVarData.merge(
*map(
lambda arg: arg[1]._get_all_var_data(),
self._args,
),
self._return._get_all_var_data(),
self._var_data,
)
@classmethod
def create(
cls,
args: Tuple[Tuple[str, Var], ...],
return_var: CustomVarOperationReturn[T],
_var_data: VarData | None = None,
) -> CustomVarOperation[T]:
"""Create a CustomVarOperation.
Args:
args: The arguments to the operation.
return_var: The return var.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The CustomVarOperation.
"""
return CustomVarOperation(
_var_name="",
_var_type=return_var._var_type,
_var_data=ImmutableVarData.merge(_var_data),
_args=args,
_return=return_var,
)

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,8 @@ from .base import (
LiteralVar,
cached_property_no_lock,
figure_out_type,
var_operation,
var_operation_return,
)
from .number import BooleanVar, NumberVar
from .sequence import ArrayVar, StringVar
@ -56,7 +58,9 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
return str
@overload
def _value_type(self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]) -> VALUE_TYPE: ...
def _value_type(
self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]],
) -> Type[VALUE_TYPE]: ...
@overload
def _value_type(self) -> Type: ...
@ -79,7 +83,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The keys of the object.
"""
return ObjectKeysOperation.create(self)
return object_keys_operation(self)
@overload
def values(
@ -95,7 +99,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The values of the object.
"""
return ObjectValuesOperation.create(self)
return object_values_operation(self)
@overload
def entries(
@ -111,9 +115,9 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The entries of the object.
"""
return ObjectEntriesOperation.create(self)
return object_entries_operation(self)
def merge(self, other: ObjectVar) -> ObjectMergeOperation:
def merge(self, other: ObjectVar):
"""Merge two objects.
Args:
@ -122,7 +126,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The merged object.
"""
return ObjectMergeOperation.create(self, other)
return object_merge_operation(self, other)
# NoReturn is used here to catch when key value is Any
@overload
@ -270,7 +274,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The result of the check.
"""
return ObjectHasOwnProperty.create(self, key)
return object_has_own_property_operation(self, key)
@dataclasses.dataclass(
@ -387,206 +391,71 @@ class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar):
)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ObjectToArrayOperation(CachedVarOperation, ArrayVar):
"""Base class for object to array operations."""
_value: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the operation.
Raises:
NotImplementedError: Must implement _cached_var_name.
"""
raise NotImplementedError(
"ObjectToArrayOperation must implement _cached_var_name"
)
@classmethod
def create(
cls,
value: ObjectVar,
_var_type: GenericType | None = None,
_var_data: VarData | None = None,
) -> ObjectToArrayOperation:
"""Create the object to array operation.
@var_operation
def object_keys_operation(value: ObjectVar):
"""Get the keys of an object.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
value: The object to get the keys from.
Returns:
The object to array operation.
The keys of the object.
"""
return cls(
_var_name="",
_var_type=list if _var_type is None else _var_type,
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
return var_operation_return(
js_expression=f"Object.keys({value})",
var_type=List[str],
)
class ObjectKeysOperation(ObjectToArrayOperation):
"""Operation to get the keys of an object."""
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the operation.
Returns:
The name of the operation.
"""
return f"Object.keys({str(self._value)})"
@classmethod
def create(
cls,
value: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectKeysOperation:
"""Create the object keys operation.
@var_operation
def object_values_operation(value: ObjectVar):
"""Get the values of an object.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
value: The object to get the values from.
Returns:
The object keys operation.
The values of the object.
"""
return cls(
_var_name="",
_var_type=List[str],
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
return var_operation_return(
js_expression=f"Object.values({value})",
var_type=List[value._value_type()],
)
class ObjectValuesOperation(ObjectToArrayOperation):
"""Operation to get the values of an object."""
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the operation.
Returns:
The name of the operation.
"""
return f"Object.values({str(self._value)})"
@classmethod
def create(
cls,
value: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectValuesOperation:
"""Create the object values operation.
@var_operation
def object_entries_operation(value: ObjectVar):
"""Get the entries of an object.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
value: The object to get the entries from.
Returns:
The object values operation.
The entries of the object.
"""
return cls(
_var_name="",
_var_type=List[value._value_type()],
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
return var_operation_return(
js_expression=f"Object.entries({value})",
var_type=List[Tuple[str, value._value_type()]],
)
class ObjectEntriesOperation(ObjectToArrayOperation):
"""Operation to get the entries of an object."""
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the operation.
Returns:
The name of the operation.
"""
return f"Object.entries({str(self._value)})"
@classmethod
def create(
cls,
value: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectEntriesOperation:
"""Create the object entries operation.
@var_operation
def object_merge_operation(lhs: ObjectVar, rhs: ObjectVar):
"""Merge two objects.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
lhs: The first object to merge.
rhs: The second object to merge.
Returns:
The object entries operation.
The merged object.
"""
return cls(
_var_name="",
_var_type=List[Tuple[str, value._value_type()]],
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ObjectMergeOperation(CachedVarOperation, ObjectVar):
"""Operation to merge two objects."""
_lhs: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
_rhs: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the operation.
Returns:
The name of the operation.
"""
return f"({{...{str(self._lhs)}, ...{str(self._rhs)}}})"
@classmethod
def create(
cls,
lhs: ObjectVar,
rhs: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectMergeOperation:
"""Create the object merge operation.
Args:
lhs: The left object to merge.
rhs: The right object to merge.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object merge operation.
"""
# TODO: Figure out how to merge the types
return cls(
_var_name="",
_var_type=lhs._var_type,
_var_data=ImmutableVarData.merge(_var_data),
_lhs=lhs,
_rhs=rhs,
return var_operation_return(
js_expression=f"({{...{lhs}, ...{rhs}}})",
var_type=Dict[
Union[lhs._key_type(), rhs._key_type()],
Union[lhs._value_type(), rhs._value_type()],
],
)
@ -688,49 +557,18 @@ class ToObjectOperation(CachedVarOperation, ObjectVar):
)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ObjectHasOwnProperty(CachedVarOperation, BooleanVar):
"""Operation to check if an object has a property."""
_object: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
_key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the operation.
Returns:
The name of the operation.
"""
return f"{str(self._object)}.hasOwnProperty({str(self._key)})"
@classmethod
def create(
cls,
object: ObjectVar,
key: Var | Any,
_var_data: VarData | None = None,
) -> ObjectHasOwnProperty:
"""Create the object has own property operation.
@var_operation
def object_has_own_property_operation(object: ObjectVar, key: Var):
"""Check if an object has a key.
Args:
object: The object to check.
key: The key to check.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object has own property operation.
The result of the check.
"""
return cls(
_var_name="",
_var_type=bool,
_var_data=ImmutableVarData.merge(_var_data),
_object=object,
_key=key if isinstance(key, Var) else LiteralVar.create(key),
return var_operation_return(
js_expression=f"{object}.hasOwnProperty({key})",
var_type=bool,
)

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@ import alembic.runtime.environment
import alembic.script
import alembic.util
import sqlalchemy
import sqlalchemy.exc
import sqlalchemy.orm
from reflex import constants
@ -51,6 +52,27 @@ def get_engine(url: str | None = None) -> sqlalchemy.engine.Engine:
return sqlmodel.create_engine(url, echo=echo_db_query, connect_args=connect_args)
async def get_db_status() -> bool:
"""Checks the status of the database connection.
Attempts to connect to the database and execute a simple query to verify connectivity.
Returns:
bool: The status of the database connection:
- True: The database is accessible.
- False: The database is not accessible.
"""
status = True
try:
engine = get_engine()
with engine.connect() as connection:
connection.execute(sqlalchemy.text("SELECT 1"))
except sqlalchemy.exc.OperationalError:
status = False
return status
SQLModelOrSqlAlchemy = Union[
Type[sqlmodel.SQLModel], Type[sqlalchemy.orm.DeclarativeBase]
]

View File

@ -11,6 +11,7 @@ import os
import uuid
from abc import ABC, abstractmethod
from collections import defaultdict
from pathlib import Path
from types import FunctionType, MethodType
from typing import (
TYPE_CHECKING,
@ -23,6 +24,7 @@ from typing import (
Optional,
Sequence,
Set,
Tuple,
Type,
Union,
cast,
@ -52,7 +54,7 @@ from reflex.event import (
EventSpec,
fix_events,
)
from reflex.utils import console, format, prerequisites, types
from reflex.utils import console, format, path_ops, prerequisites, types
from reflex.utils.exceptions import ImmutableStateError, LockExpiredError
from reflex.utils.exec import is_testing_env
from reflex.utils.serializers import SerializedType, serialize, serializer
@ -2339,7 +2341,7 @@ class StateManager(Base, ABC):
token_expiration=config.redis_token_expiration,
lock_expiration=config.redis_lock_expiration,
)
return StateManagerMemory(state=state)
return StateManagerDisk(state=state)
@abstractmethod
async def get_state(self, token: str) -> BaseState:
@ -2446,6 +2448,258 @@ class StateManagerMemory(StateManager):
await self.set_state(token, state)
def _default_token_expiration() -> int:
"""Get the default token expiration time.
Returns:
The default token expiration time.
"""
return get_config().redis_token_expiration
def _serialize_type(type_: Any) -> str:
"""Serialize a type.
Args:
type_: The type to serialize.
Returns:
The serialized type.
"""
if not inspect.isclass(type_):
return f"{type_}"
return f"{type_.__module__}.{type_.__qualname__}"
def state_to_schema(
state: BaseState,
) -> List[
Tuple[
str,
str,
Any,
Union[bool, None],
]
]:
"""Convert a state to a schema.
Args:
state: The state to convert to a schema.
Returns:
The schema.
"""
return list(
sorted(
(
field_name,
model_field.name,
_serialize_type(model_field.type_),
(
model_field.required
if isinstance(model_field.required, bool)
else None
),
)
for field_name, model_field in state.__fields__.items()
)
)
class StateManagerDisk(StateManager):
"""A state manager that stores states in memory."""
# The mapping of client ids to states.
states: Dict[str, BaseState] = {}
# The mutex ensures the dict of mutexes is updated exclusively
_state_manager_lock = asyncio.Lock()
# The dict of mutexes for each client
_states_locks: Dict[str, asyncio.Lock] = pydantic.PrivateAttr({})
# The token expiration time (s).
token_expiration: int = pydantic.Field(default_factory=_default_token_expiration)
class Config:
"""The Pydantic config."""
fields = {
"_states_locks": {"exclude": True},
}
keep_untouched = (functools.cached_property,)
def __init__(self, state: Type[BaseState]):
"""Create a new state manager.
Args:
state: The state class to use.
"""
super().__init__(state=state)
path_ops.mkdir(self.states_directory)
self._purge_expired_states()
@functools.cached_property
def states_directory(self) -> Path:
"""Get the states directory.
Returns:
The states directory.
"""
return prerequisites.get_web_dir() / constants.Dirs.STATES
def _purge_expired_states(self):
"""Purge expired states from the disk."""
import time
for path in path_ops.ls(self.states_directory):
# check path is a pickle file
if path.suffix != ".pkl":
continue
# load last edited field from file
last_edited = path.stat().st_mtime
# check if the file is older than the token expiration time
if time.time() - last_edited > self.token_expiration:
# remove the file
path.unlink()
def token_path(self, token: str) -> Path:
"""Get the path for a token.
Args:
token: The token to get the path for.
Returns:
The path for the token.
"""
return (self.states_directory / f"{token}.pkl").absolute()
async def load_state(self, token: str, root_state: BaseState) -> BaseState:
"""Load a state object based on the provided token.
Args:
token: The token used to identify the state object.
root_state: The root state object.
Returns:
The loaded state object.
"""
if token in self.states:
return self.states[token]
client_token, substate_address = _split_substate_key(token)
token_path = self.token_path(token)
if token_path.exists():
try:
with token_path.open(mode="rb") as file:
(substate_schema, substate) = dill.load(file)
if substate_schema == state_to_schema(substate):
await self.populate_substates(client_token, substate, root_state)
return substate
except Exception:
pass
return root_state.get_substate(substate_address.split(".")[1:])
async def populate_substates(
self, client_token: str, state: BaseState, root_state: BaseState
):
"""Populate the substates of a state object.
Args:
client_token: The client token.
state: The state object to populate.
root_state: The root state object.
"""
for substate in state.get_substates():
substate_token = _substate_key(client_token, substate)
substate = await self.load_state(substate_token, root_state)
state.substates[substate.get_name()] = substate
substate.parent_state = state
@override
async def get_state(
self,
token: str,
) -> BaseState:
"""Get the state for a token.
Args:
token: The token to get the state for.
Returns:
The state for the token.
"""
client_token, substate_address = _split_substate_key(token)
root_state_token = _substate_key(client_token, substate_address.split(".")[0])
return await self.load_state(
root_state_token, self.state(_reflex_internal_init=True)
)
async def set_state_for_substate(self, client_token: str, substate: BaseState):
"""Set the state for a substate.
Args:
client_token: The client token.
substate: The substate to set.
"""
substate_token = _substate_key(client_token, substate)
self.states[substate_token] = substate
state_dilled = dill.dumps((state_to_schema(substate), substate))
if not self.states_directory.exists():
self.states_directory.mkdir(parents=True, exist_ok=True)
self.token_path(substate_token).write_bytes(state_dilled)
for substate_substate in substate.substates.values():
await self.set_state_for_substate(client_token, substate_substate)
@override
async def set_state(self, token: str, state: BaseState):
"""Set the state for a token.
Args:
token: The token to set the state for.
state: The state to set.
"""
client_token, substate = _split_substate_key(token)
await self.set_state_for_substate(client_token, state)
@override
@contextlib.asynccontextmanager
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
"""Modify the state for a token while holding exclusive lock.
Args:
token: The token to modify the state for.
Yields:
The state for the token.
"""
# Memory state manager ignores the substate suffix and always returns the top-level state.
client_token, substate = _split_substate_key(token)
if client_token not in self._states_locks:
async with self._state_manager_lock:
if client_token not in self._states_locks:
self._states_locks[client_token] = asyncio.Lock()
async with self._states_locks[client_token]:
state = await self.get_state(token)
yield state
await self.set_state(token, state)
# Workaround https://github.com/cloudpipe/cloudpickle/issues/408 for dynamic pydantic classes
if not isinstance(State.validate.__func__, FunctionType):
cython_function_or_method = type(State.validate.__func__)
@ -2474,15 +2728,6 @@ def _default_lock_expiration() -> int:
return get_config().redis_lock_expiration
def _default_token_expiration() -> int:
"""Get the default token expiration time.
Returns:
The default token expiration time.
"""
return get_config().redis_token_expiration
class StateManagerRedis(StateManager):
"""A state manager that stores states in redis."""

View File

@ -45,6 +45,8 @@ import reflex.utils.prerequisites
import reflex.utils.processes
from reflex.state import (
BaseState,
StateManager,
StateManagerDisk,
StateManagerMemory,
StateManagerRedis,
reload_state_module,
@ -126,7 +128,7 @@ class AppHarness:
frontend_output_thread: Optional[threading.Thread] = None
backend_thread: Optional[threading.Thread] = None
backend: Optional[uvicorn.Server] = None
state_manager: Optional[StateManagerMemory | StateManagerRedis] = None
state_manager: Optional[StateManager] = None
_frontends: list["WebDriver"] = dataclasses.field(default_factory=list)
_decorated_pages: list = dataclasses.field(default_factory=list)
@ -290,6 +292,8 @@ class AppHarness:
if isinstance(self.app_instance._state_manager, StateManagerRedis):
# Create our own redis connection for testing.
self.state_manager = StateManagerRedis.create(self.app_instance.state)
elif isinstance(self.app_instance._state_manager, StateManagerDisk):
self.state_manager = StateManagerDisk.create(self.app_instance.state)
else:
self.state_manager = self.app_instance._state_manager
@ -327,6 +331,7 @@ class AppHarness:
)
)
self.backend.shutdown = self._get_backend_shutdown_handler()
with chdir(self.app_path):
self.backend_thread = threading.Thread(target=self.backend.run)
self.backend_thread.start()
@ -787,7 +792,7 @@ class AppHarness:
raise RuntimeError("App is not running.")
state_manager = self.app_instance.state_manager
assert isinstance(
state_manager, StateManagerMemory
state_manager, (StateManagerMemory, StateManagerDisk)
), "Only works with memory state manager"
if not self._poll_for(
target=lambda: state_manager.states,

View File

@ -79,3 +79,11 @@ class LockExpiredError(ReflexError):
class MatchTypeError(ReflexError, TypeError):
"""Raised when the return types of match cases are different."""
class EventHandlerArgMismatch(ReflexError, TypeError):
"""Raised when the number of args accepted by an EventHandler is differs from that provided by the event trigger."""
class EventFnArgMismatch(ReflexError, TypeError):
"""Raised when the number of args accepted by a lambda differs from that provided by the event trigger."""

View File

@ -18,22 +18,11 @@ from reflex import constants
from reflex.config import get_config
from reflex.utils import console, path_ops
from reflex.utils.prerequisites import get_web_dir
from reflex.utils.watch import AssetFolderWatch
# For uvicorn windows bug fix (#2335)
frontend_process = None
def start_watching_assets_folder(root):
"""Start watching assets folder.
Args:
root: root path of the project.
"""
asset_watch = AssetFolderWatch(root)
asset_watch.start()
def detect_package_change(json_file_path: str) -> str:
"""Calculates the SHA-256 hash of a JSON file and returns it as a hexadecimal string.
@ -149,8 +138,6 @@ def run_frontend(root: Path, port: str, backend_present=True):
"""
from reflex.utils import prerequisites
# Start watching asset folder.
start_watching_assets_folder(root)
# validate dependencies before run
prerequisites.validate_frontend_dependencies(init=False)
@ -215,7 +202,6 @@ def run_backend(
log_level=loglevel.value,
reload=True,
reload_dirs=[config.app_name],
reload_excludes=[str(web_dir)],
)

View File

@ -81,6 +81,18 @@ def mkdir(path: str | Path):
Path(path).mkdir(parents=True, exist_ok=True)
def ls(path: str | Path) -> list[Path]:
"""List the contents of a directory.
Args:
path: The path to the directory.
Returns:
A list of paths to the contents of the directory.
"""
return list(Path(path).iterdir())
def ln(src: str | Path, dest: str | Path, overwrite: bool = False) -> bool:
"""Create a symbolic link.
@ -197,4 +209,4 @@ def find_replace(directory: str | Path, find: str, replace: str):
filepath = Path(root, file)
text = filepath.read_text(encoding="utf-8")
text = re.sub(find, replace, text)
filepath.write_text(text)
filepath.write_text(text, encoding="utf-8")

View File

@ -28,6 +28,7 @@ import typer
from alembic.util.exc import CommandError
from packaging import version
from redis import Redis as RedisSync
from redis import exceptions
from redis.asyncio import Redis
from reflex import constants, model
@ -344,6 +345,30 @@ def parse_redis_url() -> str | dict | None:
return dict(host=redis_url, port=int(redis_port), db=0)
async def get_redis_status() -> bool | None:
"""Checks the status of the Redis connection.
Attempts to connect to Redis and send a ping command to verify connectivity.
Returns:
bool or None: The status of the Redis connection:
- True: Redis is accessible and responding.
- False: Redis is not accessible due to a connection error.
- None: Redis not used i.e redis_url is not set in rxconfig.
"""
try:
status = True
redis_client = get_redis_sync()
if redis_client is not None:
redis_client.ping()
else:
status = None
except exceptions.RedisError:
status = False
return status
def validate_app_name(app_name: str | None = None) -> str:
"""Validate the app name.

View File

@ -245,9 +245,14 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
"""
from reflex.model import Model
try:
attr = getattr(cls, name, None)
except NotImplementedError:
attr = None
if hint := get_property_hint(attr):
return hint
if (
hasattr(cls, "__fields__")
and name in cls.__fields__

View File

@ -1,96 +0,0 @@
"""General utility functions."""
import contextlib
import os
import shutil
import time
from watchdog.events import FileSystemEvent, FileSystemEventHandler
from watchdog.observers import Observer
from reflex.constants import Dirs
from reflex.utils.prerequisites import get_web_dir
class AssetFolderWatch:
"""Asset folder watch class."""
def __init__(self, root):
"""Initialize the Watch Class.
Args:
root: root path of the public.
"""
self.path = str(root / Dirs.APP_ASSETS)
self.event_handler = AssetFolderHandler(root)
def start(self):
"""Start watching asset folder."""
self.observer = Observer()
self.observer.schedule(self.event_handler, self.path, recursive=True)
self.observer.start()
class AssetFolderHandler(FileSystemEventHandler):
"""Asset folder event handler."""
def __init__(self, root):
"""Initialize the AssetFolderHandler Class.
Args:
root: root path of the public.
"""
super().__init__()
self.root = root
def on_modified(self, event: FileSystemEvent):
"""Event handler when a file or folder was modified.
This is called every time after a file is created, modified and deleted.
Args:
event: Event information.
"""
dest_path = self.get_dest_path(event.src_path)
# wait 1 sec for fully saved
time.sleep(1)
if os.path.isfile(event.src_path):
with contextlib.suppress(PermissionError):
shutil.copyfile(event.src_path, dest_path)
if os.path.isdir(event.src_path):
if os.path.exists(dest_path):
shutil.rmtree(dest_path)
with contextlib.suppress(PermissionError):
shutil.copytree(event.src_path, dest_path)
def on_deleted(self, event: FileSystemEvent):
"""Event hander when a file or folder was deleted.
Args:
event: Event infomation.
"""
dest_path = self.get_dest_path(event.src_path)
if os.path.isfile(dest_path):
# when event is about a file, pass
# this will be deleted at on_modified function
return
if os.path.exists(dest_path):
shutil.rmtree(dest_path)
def get_dest_path(self, src_path: str) -> str:
"""Get public file path.
Args:
src_path: The asset file path.
Returns:
The public file path.
"""
return src_path.replace(
str(self.root / Dirs.APP_ASSETS),
str(self.root / get_web_dir() / Dirs.PUBLIC),
)

View File

@ -6,7 +6,6 @@ import contextlib
import dataclasses
import datetime
import dis
import functools
import inspect
import json
import random
@ -2532,7 +2531,6 @@ def computed_var(
auto_deps: bool = True,
interval: Optional[Union[datetime.timedelta, int]] = None,
backend: bool | None = None,
_deprecated_cached_var: bool = False,
**kwargs,
) -> ComputedVar | Callable[[Callable[[BaseState], Any]], ComputedVar]:
"""A ComputedVar decorator with or without kwargs.
@ -2545,7 +2543,6 @@ def computed_var(
auto_deps: Whether var dependencies should be auto-determined.
interval: Interval at which the computed var should be updated.
backend: Whether the computed var is a backend var.
_deprecated_cached_var: Indicate usage of deprecated cached_var partial function.
**kwargs: additional attributes to set on the instance
Returns:
@ -2555,14 +2552,6 @@ def computed_var(
ValueError: If caching is disabled and an update interval is set.
VarDependencyError: If user supplies dependencies without caching.
"""
if _deprecated_cached_var:
console.deprecate(
feature_name="cached_var",
reason=("Use @rx.var(cache=True) instead of @rx.cached_var."),
deprecation_version="0.5.6",
removal_version="0.6.0",
)
if cache is False and interval is not None:
raise ValueError("Cannot set update interval without caching.")
@ -2587,9 +2576,6 @@ def computed_var(
return wrapper
# Partial function of computed_var with cache=True
cached_var = functools.partial(computed_var, cache=True, _deprecated_cached_var=True)
VAR_CALLABLE = Callable[[Any], Var]

View File

@ -67,11 +67,11 @@ def test_color(color, expected):
[
(
rx.cond(True, rx.color("mint"), rx.color("tomato", 5)),
'(Boolean(true) ? "var(--mint-7)" : "var(--tomato-5)")',
'(true ? "var(--mint-7)" : "var(--tomato-5)")',
),
(
rx.cond(True, rx.color(ColorState.color), rx.color(ColorState.color, 5)), # type: ignore
f'(Boolean(true) ? ("var(--"+{str(color_state_name)}.color+"-7)") : ("var(--"+{str(color_state_name)}.color+"-5)"))',
f'(true ? ("var(--"+{str(color_state_name)}.color+"-7)") : ("var(--"+{str(color_state_name)}.color+"-5)"))',
),
(
rx.match(

View File

@ -23,7 +23,7 @@ def cond_state(request):
def test_f_string_cond_interpolation():
# make sure backticks inside interpolation don't get escaped
var = LiteralVar.create(f"x {cond(True, 'a', 'b')}")
assert str(var) == '("x "+(Boolean(true) ? "a" : "b"))'
assert str(var) == '("x "+(true ? "a" : "b"))'
@pytest.mark.parametrize(
@ -97,7 +97,7 @@ def test_prop_cond(c1: Any, c2: Any):
c1 = json.dumps(c1)
if not isinstance(c2, Var):
c2 = json.dumps(c2)
assert str(prop_cond) == f"(Boolean(true) ? {c1} : {c2})"
assert str(prop_cond) == f"(true ? {c1} : {c2})"
def test_cond_no_mix():
@ -141,8 +141,7 @@ def test_cond_computed_var():
state_name = format_state_name(CondStateComputed.get_full_name())
assert (
str(comp)
== f"(Boolean(true) ? {state_name}.computed_int : {state_name}.computed_str)"
str(comp) == f"(true ? {state_name}.computed_int : {state_name}.computed_str)"
)
assert comp._var_type == Union[int, str]

View File

@ -1,6 +1,6 @@
import pytest
from reflex.components.lucide.icon import LUCIDE_ICON_LIST, RENAMED_ICONS_05, Icon
from reflex.components.lucide.icon import LUCIDE_ICON_LIST, Icon
from reflex.utils import format
@ -10,16 +10,6 @@ def test_icon(tag):
assert icon.alias == f"Lucide{format.to_title_case(tag)}Icon"
RENAMED_TAGS = [tag for tag in RENAMED_ICONS_05.items()]
@pytest.mark.parametrize("tag, new_tag", RENAMED_TAGS)
def test_icon_renamed_tags(tag, new_tag):
Icon.create(tag)
# TODO: need a PR so we can pass the following test. Currently it fails and uses the old tag as the import.
# assert icon.alias == f"Lucide{format.to_title_case(new_tag)}Icon"
def test_icon_missing_tag():
with pytest.raises(AttributeError):
_ = Icon.create()

View File

@ -22,6 +22,7 @@ from reflex.ivars.base import LiteralVar
from reflex.state import BaseState
from reflex.style import Style
from reflex.utils import imports
from reflex.utils.exceptions import EventFnArgMismatch, EventHandlerArgMismatch
from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports
from reflex.vars import BaseVar, Var, VarData
@ -79,6 +80,8 @@ def component2() -> Type[Component]:
# A test list prop.
arr: Var[List[str]]
on_prop_event: EventHandler[lambda e0: [e0]]
def get_event_triggers(self) -> Dict[str, Any]:
"""Test controlled triggers.
@ -496,7 +499,7 @@ def test_get_props(component1, component2):
component2: A test component.
"""
assert component1.get_props() == {"text", "number", "text_or_number"}
assert component2.get_props() == {"arr"}
assert component2.get_props() == {"arr", "on_prop_event"}
@pytest.mark.parametrize(
@ -574,7 +577,7 @@ def test_get_event_triggers(component1, component2):
assert component1().get_event_triggers().keys() == default_triggers
assert (
component2().get_event_triggers().keys()
== {"on_open", "on_close"} | default_triggers
== {"on_open", "on_close", "on_prop_event"} | default_triggers
)
@ -888,18 +891,105 @@ def test_invalid_event_handler_args(component2, test_state):
component2: A test component.
test_state: A test state.
"""
# Uncontrolled event handlers should not take args.
# This is okay.
component2.create(on_click=test_state.do_something)
# This is not okay.
with pytest.raises(ValueError):
# EventHandler args must match
with pytest.raises(EventHandlerArgMismatch):
component2.create(on_click=test_state.do_something_arg)
with pytest.raises(EventHandlerArgMismatch):
component2.create(on_open=test_state.do_something)
with pytest.raises(EventHandlerArgMismatch):
component2.create(on_prop_event=test_state.do_something)
# Multiple EventHandler args: all must match
with pytest.raises(EventHandlerArgMismatch):
component2.create(
on_click=[test_state.do_something_arg, test_state.do_something]
)
with pytest.raises(EventHandlerArgMismatch):
component2.create(
on_open=[test_state.do_something_arg, test_state.do_something]
)
# However lambdas are okay.
with pytest.raises(EventHandlerArgMismatch):
component2.create(
on_prop_event=[test_state.do_something_arg, test_state.do_something]
)
# lambda cannot return weird values.
with pytest.raises(ValueError):
component2.create(on_click=lambda: 1)
with pytest.raises(ValueError):
component2.create(on_click=lambda: [1])
with pytest.raises(ValueError):
component2.create(
on_click=lambda: (test_state.do_something_arg(1), test_state.do_something)
)
# lambda signature must match event trigger.
with pytest.raises(EventFnArgMismatch):
component2.create(on_click=lambda _: test_state.do_something_arg(1))
with pytest.raises(EventFnArgMismatch):
component2.create(on_open=lambda: test_state.do_something)
with pytest.raises(EventFnArgMismatch):
component2.create(on_prop_event=lambda: test_state.do_something)
# lambda returning EventHandler must match spec
with pytest.raises(EventHandlerArgMismatch):
component2.create(on_click=lambda: test_state.do_something_arg)
with pytest.raises(EventHandlerArgMismatch):
component2.create(on_open=lambda _: test_state.do_something)
with pytest.raises(EventHandlerArgMismatch):
component2.create(on_prop_event=lambda _: test_state.do_something)
# Mixed EventSpec and EventHandler must match spec.
with pytest.raises(EventHandlerArgMismatch):
component2.create(
on_click=lambda: [
test_state.do_something_arg(1),
test_state.do_something_arg,
]
)
with pytest.raises(EventHandlerArgMismatch):
component2.create(
on_open=lambda _: [test_state.do_something_arg(1), test_state.do_something]
)
with pytest.raises(EventHandlerArgMismatch):
component2.create(
on_prop_event=lambda _: [
test_state.do_something_arg(1),
test_state.do_something,
]
)
def test_valid_event_handler_args(component2, test_state):
"""Test that an valid event handler args do not raise exception.
Args:
component2: A test component.
test_state: A test state.
"""
# Uncontrolled event handlers should not take args.
component2.create(on_click=test_state.do_something)
component2.create(on_click=test_state.do_something_arg(1))
# Controlled event handlers should take args.
component2.create(on_open=test_state.do_something_arg)
component2.create(on_prop_event=test_state.do_something_arg)
# Using a partial event spec bypasses arg validation (ignoring the args).
component2.create(on_open=test_state.do_something())
component2.create(on_prop_event=test_state.do_something())
# lambda returning EventHandler is okay if the spec matches.
component2.create(on_click=lambda: test_state.do_something)
component2.create(on_open=lambda _: test_state.do_something_arg)
component2.create(on_prop_event=lambda _: test_state.do_something_arg)
# lambda can always return an EventSpec.
component2.create(on_click=lambda: test_state.do_something_arg(1))
component2.create(on_open=lambda _: test_state.do_something_arg(1))
component2.create(on_prop_event=lambda _: test_state.do_something_arg(1))
# Return EventSpec and EventHandler (no arg).
component2.create(
on_click=lambda: [test_state.do_something_arg(1), test_state.do_something]
)
@ -907,9 +997,24 @@ def test_invalid_event_handler_args(component2, test_state):
on_click=lambda: [test_state.do_something_arg(1), test_state.do_something()]
)
# Controlled event handlers should take args.
# This is okay.
component2.create(on_open=test_state.do_something_arg)
# Return 2 EventSpec.
component2.create(
on_open=lambda _: [test_state.do_something_arg(1), test_state.do_something()]
)
component2.create(
on_prop_event=lambda _: [
test_state.do_something_arg(1),
test_state.do_something(),
]
)
# Return EventHandler (1 arg) and EventSpec.
component2.create(
on_open=lambda _: [test_state.do_something_arg, test_state.do_something()]
)
component2.create(
on_prop_event=lambda _: [test_state.do_something_arg, test_state.do_something()]
)
def test_get_hooks_nested(component1, component2, component3):
@ -1613,62 +1718,6 @@ def test_rename_props():
assert 'renamed_prop3={"prop3_2"}' in rendered_c2["props"]
def test_deprecated_props(capsys):
"""Assert that deprecated underscore suffix props are translated.
Args:
capsys: Pytest fixture for capturing stdout and stderr.
"""
class C1(Component):
tag = "C1"
type: Var[str]
min: Var[str]
max: Var[str]
# No warnings are emitted when using the new prop names.
c1_1 = C1.create(type="type1", min="min1", max="max1")
out_err = capsys.readouterr()
assert not out_err.err
assert not out_err.out
c1_1_render = c1_1.render()
assert 'type={"type1"}' in c1_1_render["props"]
assert 'min={"min1"}' in c1_1_render["props"]
assert 'max={"max1"}' in c1_1_render["props"]
# Deprecation warning is emitted with underscore suffix,
# but the component still works.
c1_2 = C1.create(type_="type2", min_="min2", max_="max2")
out_err = capsys.readouterr()
assert out_err.out.count("DeprecationWarning:") == 3
assert not out_err.err
c1_2_render = c1_2.render()
assert 'type={"type2"}' in c1_2_render["props"]
assert 'min={"min2"}' in c1_2_render["props"]
assert 'max={"max2"}' in c1_2_render["props"]
class C2(Component):
tag = "C2"
type_: Var[str]
min_: Var[str]
max_: Var[str]
# No warnings are emitted if the actual prop has an underscore suffix
c2_1 = C2.create(type_="type1", min_="min1", max_="max1")
out_err = capsys.readouterr()
assert not out_err.err
assert not out_err.out
c2_1_render = c2_1.render()
assert 'type={"type1"}' in c2_1_render["props"]
assert 'min={"min1"}' in c2_1_render["props"]
assert 'max={"max1"}' in c2_1_render["props"]
def test_custom_component_get_imports():
class Inner(Component):
tag = "Inner"

View File

@ -42,6 +42,7 @@ from reflex.state import (
OnLoadInternalState,
RouterData,
State,
StateManagerDisk,
StateManagerMemory,
StateManagerRedis,
StateUpdate,
@ -1395,7 +1396,9 @@ def test_app_state_manager():
app.state_manager
app._enable_state()
assert app.state_manager is not None
assert isinstance(app.state_manager, (StateManagerMemory, StateManagerRedis))
assert isinstance(
app.state_manager, (StateManagerMemory, StateManagerDisk, StateManagerRedis)
)
def test_generate_component():

View File

@ -94,6 +94,15 @@ class SQLAClass(SQLABase):
"""
return self.name
@hybrid_property
def first_label(self) -> Optional[SQLALabel]:
"""First label property.
Returns:
First label
"""
return self.labels[0] if self.labels else None
class ModelClass(rx.Model):
"""Test reflex model."""
@ -125,6 +134,15 @@ class ModelClass(rx.Model):
"""
return self.name
@property
def first_label(self) -> Optional[SQLALabel]:
"""First label property.
Returns:
First label
"""
return self.labels[0] if self.labels else None
class BaseClass(rx.Base):
"""Test rx.Base class."""
@ -156,6 +174,15 @@ class BaseClass(rx.Base):
"""
return self.name
@property
def first_label(self) -> Optional[SQLALabel]:
"""First label property.
Returns:
First label
"""
return self.labels[0] if self.labels else None
class BareClass:
"""Bare python class."""
@ -187,6 +214,15 @@ class BareClass:
"""
return self.name
@property
def first_label(self) -> Optional[SQLALabel]:
"""First label property.
Returns:
First label
"""
return self.labels[0] if self.labels else None
@attrs.define
class AttrClass:
@ -219,6 +255,15 @@ class AttrClass:
"""
return self.name
@property
def first_label(self) -> Optional[SQLALabel]:
"""First label property.
Returns:
First label
"""
return self.labels[0] if self.labels else None
@pytest.fixture(
params=[
@ -254,6 +299,7 @@ def cls(request: pytest.FixtureRequest) -> type:
pytest.param("dict_str_str", Dict[str, str], id="Dict[str, str]"),
pytest.param("str_property", str, id="str_property"),
pytest.param("str_or_int_property", Union[str, int], id="str_or_int_property"),
pytest.param("first_label", Optional[SQLALabel], id="first_label"),
],
)
def test_get_attribute_access_type(cls: type, attr: str, expected: GenericType) -> None:

View File

@ -0,0 +1,106 @@
import json
from unittest.mock import MagicMock, Mock
import pytest
import sqlalchemy
from redis.exceptions import RedisError
from reflex.app import health
from reflex.model import get_db_status
from reflex.utils.prerequisites import get_redis_status
@pytest.mark.asyncio
@pytest.mark.parametrize(
"mock_redis_client, expected_status",
[
# Case 1: Redis client is available and responds to ping
(Mock(ping=lambda: None), True),
# Case 2: Redis client raises RedisError
(Mock(ping=lambda: (_ for _ in ()).throw(RedisError)), False),
# Case 3: Redis client is not used
(None, None),
],
)
async def test_get_redis_status(mock_redis_client, expected_status, mocker):
# Mock the `get_redis_sync` function to return the mock Redis client
mock_get_redis_sync = mocker.patch(
"reflex.utils.prerequisites.get_redis_sync", return_value=mock_redis_client
)
# Call the function
status = await get_redis_status()
# Verify the result
assert status == expected_status
mock_get_redis_sync.assert_called_once()
@pytest.mark.asyncio
@pytest.mark.parametrize(
"mock_engine, execute_side_effect, expected_status",
[
# Case 1: Database is accessible
(MagicMock(), None, True),
# Case 2: Database connection error (OperationalError)
(
MagicMock(),
sqlalchemy.exc.OperationalError("error", "error", "error"),
False,
),
],
)
async def test_get_db_status(mock_engine, execute_side_effect, expected_status, mocker):
# Mock get_engine to return the mock_engine
mock_get_engine = mocker.patch("reflex.model.get_engine", return_value=mock_engine)
# Mock the connection and its execute method
if mock_engine:
mock_connection = mock_engine.connect.return_value.__enter__.return_value
if execute_side_effect:
# Simulate execute method raising an exception
mock_connection.execute.side_effect = execute_side_effect
else:
# Simulate successful execute call
mock_connection.execute.return_value = None
# Call the function
status = await get_db_status()
# Verify the result
assert status == expected_status
mock_get_engine.assert_called_once()
@pytest.mark.asyncio
@pytest.mark.parametrize(
"db_status, redis_status, expected_status, expected_code",
[
# Case 1: Both services are connected
(True, True, {"status": True, "db": True, "redis": True}, 200),
# Case 2: Database not connected, Redis connected
(False, True, {"status": False, "db": False, "redis": True}, 503),
# Case 3: Database connected, Redis not connected
(True, False, {"status": False, "db": True, "redis": False}, 503),
# Case 4: Both services not connected
(False, False, {"status": False, "db": False, "redis": False}, 503),
# Case 5: Database Connected, Redis not used
(True, None, {"status": True, "db": True, "redis": False}, 200),
],
)
async def test_health(db_status, redis_status, expected_status, expected_code, mocker):
# Mock get_db_status and get_redis_status
mocker.patch("reflex.app.get_db_status", return_value=db_status)
mocker.patch(
"reflex.utils.prerequisites.get_redis_status", return_value=redis_status
)
# Call the async health function
response = await health()
print(json.loads(response.body))
print(expected_status)
# Verify the response content and status code
assert response.status_code == expected_code
assert json.loads(response.body) == expected_status

View File

@ -31,6 +31,7 @@ from reflex.state import (
RouterData,
State,
StateManager,
StateManagerDisk,
StateManagerMemory,
StateManagerRedis,
StateProxy,
@ -1586,7 +1587,7 @@ async def test_state_with_invalid_yield(capsys, mock_app):
assert "must only return/yield: None, Events or other EventHandlers" in captured.out
@pytest.fixture(scope="function", params=["in_process", "redis"])
@pytest.fixture(scope="function", params=["in_process", "disk", "redis"])
def state_manager(request) -> Generator[StateManager, None, None]:
"""Instance of state manager parametrized for redis and in-process.
@ -1600,8 +1601,11 @@ def state_manager(request) -> Generator[StateManager, None, None]:
if request.param == "redis":
if not isinstance(state_manager, StateManagerRedis):
pytest.skip("Test requires redis")
else:
elif request.param == "disk":
# explicitly NOT using redis
state_manager = StateManagerDisk(state=TestState)
assert not state_manager._states_locks
else:
state_manager = StateManagerMemory(state=TestState)
assert not state_manager._states_locks
@ -1639,7 +1643,7 @@ async def test_state_manager_modify_state(
async with state_manager.modify_state(substate_token) as state:
if isinstance(state_manager, StateManagerRedis):
assert await state_manager.redis.get(f"{token}_lock")
elif isinstance(state_manager, StateManagerMemory):
elif isinstance(state_manager, (StateManagerMemory, StateManagerDisk)):
assert token in state_manager._states_locks
assert state_manager._states_locks[token].locked()
# Should be able to write proxy objects inside mutables
@ -1649,11 +1653,11 @@ async def test_state_manager_modify_state(
# lock should be dropped after exiting the context
if isinstance(state_manager, StateManagerRedis):
assert (await state_manager.redis.get(f"{token}_lock")) is None
elif isinstance(state_manager, StateManagerMemory):
elif isinstance(state_manager, (StateManagerMemory, StateManagerDisk)):
assert not state_manager._states_locks[token].locked()
# separate instances should NOT share locks
sm2 = StateManagerMemory(state=TestState)
sm2 = state_manager.__class__(state=TestState)
assert sm2._state_manager_lock is state_manager._state_manager_lock
assert not sm2._states_locks
if state_manager._states_locks:
@ -1691,7 +1695,7 @@ async def test_state_manager_contend(
if isinstance(state_manager, StateManagerRedis):
assert (await state_manager.redis.get(f"{token}_lock")) is None
elif isinstance(state_manager, StateManagerMemory):
elif isinstance(state_manager, (StateManagerMemory, StateManagerDisk)):
assert token in state_manager._states_locks
assert not state_manager._states_locks[token].locked()
@ -1831,7 +1835,7 @@ async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App):
assert child_state is not None
parent_state = child_state.parent_state
assert parent_state is not None
if isinstance(mock_app.state_manager, StateManagerMemory):
if isinstance(mock_app.state_manager, (StateManagerMemory, StateManagerDisk)):
mock_app.state_manager.states[parent_state.router.session.client_token] = (
parent_state
)
@ -1874,7 +1878,7 @@ async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App):
# For in-process store, only one instance of the state exists
assert sp.__wrapped__ is grandchild_state
else:
# When redis is used, a new+updated instance is assigned to the proxy
# When redis or disk is used, a new+updated instance is assigned to the proxy
assert sp.__wrapped__ is not grandchild_state
sp.value2 = "42"
assert not sp._self_mutable # proxy is not mutable after exiting context
@ -2837,7 +2841,7 @@ async def test_get_state(mock_app: rx.App, token: str):
_substate_key(token, ChildState2)
)
assert isinstance(test_state, TestState)
if isinstance(mock_app.state_manager, StateManagerMemory):
if isinstance(mock_app.state_manager, (StateManagerMemory, StateManagerDisk)):
# All substates are available
assert tuple(sorted(test_state.substates)) == (
ChildState.get_name(),
@ -2916,6 +2920,15 @@ async def test_get_state(mock_app: rx.App, token: str):
ChildState2.get_name(),
ChildState3.get_name(),
)
elif isinstance(mock_app.state_manager, StateManagerDisk):
# On disk, it's a new instance
assert new_test_state is not test_state
# All substates are available
assert tuple(sorted(new_test_state.substates)) == (
ChildState.get_name(),
ChildState2.get_name(),
ChildState3.get_name(),
)
else:
# With redis, we get a whole new instance
assert new_test_state is not test_state

View File

@ -12,6 +12,7 @@ from reflex.ivars.base import (
ImmutableVar,
LiteralVar,
var_operation,
var_operation_return,
)
from reflex.ivars.function import ArgsFunctionOperation, FunctionStringVar
from reflex.ivars.number import (
@ -925,9 +926,9 @@ def test_function_var():
def test_var_operation():
@var_operation(output=NumberVar)
def add(a: Union[NumberVar, int], b: Union[NumberVar, int]) -> str:
return f"({a} + {b})"
@var_operation
def add(a: Union[NumberVar, int], b: Union[NumberVar, int]):
return var_operation_return(js_expression=f"({a} + {b})", var_type=int)
assert str(add(1, 2)) == "(1 + 2)"
assert str(add(a=4, b=-9)) == "(4 + -9)"
@ -967,14 +968,14 @@ def test_all_number_operations():
assert (
str(even_more_complicated_number)
== "!(Boolean((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) || (2 && Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))))))"
== "!(((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) || (2 && Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2)))) !== 0))"
)
assert str(LiteralNumberVar.create(5) > False) == "(5 > 0)"
assert str(LiteralBooleanVar.create(False) < 5) == "((false ? 1 : 0) < 5)"
assert str(LiteralBooleanVar.create(False) < 5) == "(Number(false) < 5)"
assert (
str(LiteralBooleanVar.create(False) < LiteralBooleanVar.create(True))
== "((false ? 1 : 0) < (true ? 1 : 0))"
== "(Number(false) < Number(true))"
)