Merge remote-tracking branch 'upstream/main' into minify-state-names-v2

This commit is contained in:
Benedikt Bartscher 2024-11-22 21:09:29 +01:00
commit d8def13530
No known key found for this signature in database
28 changed files with 821 additions and 338 deletions

230
poetry.lock generated
View File

@ -1656,22 +1656,19 @@ files = [
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.9.2" version = "2.10.1"
description = "Data validation using Python type hints" description = "Data validation using Python type hints"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, {file = "pydantic-2.10.1-py3-none-any.whl", hash = "sha256:a8d20db84de64cf4a7d59e899c2caf0fe9d660c7cfc482528e7020d7dd189a7e"},
{file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, {file = "pydantic-2.10.1.tar.gz", hash = "sha256:a4daca2dc0aa429555e0656d6bf94873a7dc5f54ee42b1f5873d666fb3f35560"},
] ]
[package.dependencies] [package.dependencies]
annotated-types = ">=0.6.0" annotated-types = ">=0.6.0"
pydantic-core = "2.23.4" pydantic-core = "2.27.1"
typing-extensions = [ typing-extensions = ">=4.12.2"
{version = ">=4.6.1", markers = "python_version < \"3.13\""},
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
]
[package.extras] [package.extras]
email = ["email-validator (>=2.0.0)"] email = ["email-validator (>=2.0.0)"]
@ -1679,100 +1676,111 @@ timezone = ["tzdata"]
[[package]] [[package]]
name = "pydantic-core" name = "pydantic-core"
version = "2.23.4" version = "2.27.1"
description = "Core functionality for Pydantic validation and serialization" description = "Core functionality for Pydantic validation and serialization"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"},
{file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"},
{file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"},
{file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"},
{file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"},
{file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"},
{file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"},
{file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"},
{file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"},
{file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"},
{file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"},
{file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"},
{file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"},
{file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"},
{file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"},
{file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"},
{file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"},
{file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"},
{file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"},
{file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"},
{file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"},
{file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"},
{file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"},
{file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"},
{file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"},
{file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"},
{file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"},
{file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"},
{file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"},
{file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"},
{file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"},
{file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"},
{file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"},
{file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"},
{file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"},
{file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"},
{file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"},
{file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"},
] ]
[package.dependencies] [package.dependencies]
@ -2198,13 +2206,13 @@ reflex = ">=0.6.0a"
[[package]] [[package]]
name = "reflex-hosting-cli" name = "reflex-hosting-cli"
version = "0.1.16" version = "0.1.17"
description = "Reflex Hosting CLI" description = "Reflex Hosting CLI"
optional = false optional = false
python-versions = "<4.0,>=3.8" python-versions = "<4.0,>=3.8"
files = [ files = [
{file = "reflex_hosting_cli-0.1.16-py3-none-any.whl", hash = "sha256:1b12c2a76a27571102cacf5ec3028fb09816a51568bff793735e66aaad886605"}, {file = "reflex_hosting_cli-0.1.17-py3-none-any.whl", hash = "sha256:cf1accec70745557a40125ffa2a8929e6ef9834808afe78e4f4a01933ac0cb67"},
{file = "reflex_hosting_cli-0.1.16.tar.gz", hash = "sha256:9c9826318fd3d388b706be70f1f500a35e355f8f86f54138e7789fd6096a4ab9"}, {file = "reflex_hosting_cli-0.1.17.tar.gz", hash = "sha256:263d8dc217eb24d4198ac0bcfd710980bd7795d9818a5e522027657f94752710"},
] ]
[package.dependencies] [package.dependencies]
@ -2350,23 +2358,23 @@ websocket-client = ">=1.8,<2.0"
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "75.5.0" version = "75.6.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
files = [ files = [
{file = "setuptools-75.5.0-py3-none-any.whl", hash = "sha256:87cb777c3b96d638ca02031192d40390e0ad97737e27b6b4fa831bea86f2f829"}, {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"},
{file = "setuptools-75.5.0.tar.gz", hash = "sha256:5c4ccb41111392671f02bb5f8436dfc5a9a7185e80500531b133f5775c4163ef"}, {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"},
] ]
[package.extras] [package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"]
core = ["importlib-metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
cover = ["pytest-cov"] cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
enabler = ["pytest-enabler (>=2.2)"] enabler = ["pytest-enabler (>=2.2)"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"]
[[package]] [[package]]
name = "shellingham" name = "shellingham"
@ -2773,13 +2781,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]] [[package]]
name = "uvicorn" name = "uvicorn"
version = "0.32.0" version = "0.32.1"
description = "The lightning-fast ASGI server." description = "The lightning-fast ASGI server."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82"}, {file = "uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e"},
{file = "uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e"}, {file = "uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175"},
] ]
[package.dependencies] [package.dependencies]
@ -2788,7 +2796,7 @@ h11 = ">=0.8"
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
@ -3033,4 +3041,4 @@ type = ["pytest-mypy"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "a610d4c4bfd852f30e69ad2fbb288c2d9cbdf49e05b9d4936fe5e9b2a7cdefdb" content-hash = "8000601d48cfc1b10d0ae18c6046cc59a50cb6c45e6d3ef4775a3203769f2154"

View File

@ -49,7 +49,7 @@ wrapt = [
{version = ">=1.11.0,<2.0", python = "<3.11"}, {version = ">=1.11.0,<2.0", python = "<3.11"},
] ]
packaging = ">=23.1,<25.0" packaging = ">=23.1,<25.0"
reflex-hosting-cli = ">=0.1.16,<2.0" reflex-hosting-cli = ">=0.1.17,<2.0"
charset-normalizer = ">=3.3.2,<4.0" charset-normalizer = ">=3.3.2,<4.0"
wheel = ">=0.42.0,<1.0" wheel = ">=0.42.0,<1.0"
build = ">=1.0.3,<2.0" build = ">=1.0.3,<2.0"

View File

@ -707,6 +707,11 @@ export const useEventLoop = (
_e.stopPropagation(); _e.stopPropagation();
} }
const combined_name = events.map((e) => e.name).join("+++"); const combined_name = events.map((e) => e.name).join("+++");
if (event_actions?.temporal) {
if (!socket.current || !socket.current.connected) {
return; // don't queue when the backend is not connected
}
}
if (event_actions?.throttle) { if (event_actions?.throttle) {
// If throttle returns false, the events are not added to the queue. // If throttle returns false, the events are not added to the queue.
if (!throttle(combined_name, event_actions.throttle)) { if (!throttle(combined_name, event_actions.throttle)) {
@ -764,7 +769,7 @@ export const useEventLoop = (
window.onunhandledrejection = function (event) { window.onunhandledrejection = function (event) {
addEvents([ addEvents([
Event(`${exception_state_name}.handle_frontend_exception`, { Event(`${exception_state_name}.handle_frontend_exception`, {
stack: event.reason.stack, stack: event.reason?.stack,
component_stack: "", component_stack: "",
}), }),
]); ]);
@ -839,11 +844,20 @@ export const useEventLoop = (
} }
}; };
const change_complete = () => addEvents(onLoadInternalEvent()); const change_complete = () => addEvents(onLoadInternalEvent());
const change_error = () => {
// Remove cached error state from router for this page, otherwise the
// page will never send on_load events again.
if (router.components[router.pathname].error) {
delete router.components[router.pathname].error;
}
}
router.events.on("routeChangeStart", change_start); router.events.on("routeChangeStart", change_start);
router.events.on("routeChangeComplete", change_complete); router.events.on("routeChangeComplete", change_complete);
router.events.on("routeChangeError", change_error);
return () => { return () => {
router.events.off("routeChangeStart", change_start); router.events.off("routeChangeStart", change_start);
router.events.off("routeChangeComplete", change_complete); router.events.off("routeChangeComplete", change_complete);
router.events.off("routeChangeError", change_error);
}; };
}, [router]); }, [router]);

View File

@ -264,6 +264,7 @@ _MAPPING: dict = {
"experimental": ["_x"], "experimental": ["_x"],
"admin": ["AdminDash"], "admin": ["AdminDash"],
"app": ["App", "UploadFile"], "app": ["App", "UploadFile"],
"assets": ["asset"],
"base": ["Base"], "base": ["Base"],
"components.component": [ "components.component": [
"Component", "Component",

View File

@ -19,6 +19,7 @@ from . import vars as vars
from .admin import AdminDash as AdminDash from .admin import AdminDash as AdminDash
from .app import App as App from .app import App as App
from .app import UploadFile as UploadFile from .app import UploadFile as UploadFile
from .assets import asset as asset
from .base import Base as Base from .base import Base as Base
from .components import el as el from .components import el as el
from .components import lucide as lucide from .components import lucide as lucide

95
reflex/assets.py Normal file
View File

@ -0,0 +1,95 @@
"""Helper functions for adding assets to the app."""
import inspect
from pathlib import Path
from typing import Optional
from reflex import constants
from reflex.utils.exec import is_backend_only
def asset(
path: str,
shared: bool = False,
subfolder: Optional[str] = None,
_stack_level: int = 1,
) -> str:
"""Add an asset to the app, either shared as a symlink or local.
Shared/External/Library assets:
Place the file next to your including python file.
Links the file to the app's external assets directory.
Example:
```python
# my_custom_javascript.js is a shared asset located next to the including python file.
rx.script(src=rx.asset(path="my_custom_javascript.js", shared=True))
rx.image(src=rx.asset(path="test_image.png", shared=True, subfolder="subfolder"))
```
Local/Internal assets:
Place the file in the app's assets/ directory.
Example:
```python
# local_image.png is an asset located in the app's assets/ directory. It cannot be shared when developing a library.
rx.image(src=rx.asset(path="local_image.png"))
```
Args:
path: The relative path of the asset.
subfolder: The directory to place the shared asset in.
shared: Whether to expose the asset to other apps.
_stack_level: The stack level to determine the calling file, defaults to
the immediate caller 1. When using rx.asset via a helper function,
increase this number for each helper function in the stack.
Raises:
FileNotFoundError: If the file does not exist.
ValueError: If subfolder is provided for local assets.
Returns:
The relative URL to the asset.
"""
assets = constants.Dirs.APP_ASSETS
backend_only = is_backend_only()
# Local asset handling
if not shared:
cwd = Path.cwd()
src_file_local = cwd / assets / path
if subfolder is not None:
raise ValueError("Subfolder is not supported for local assets.")
if not backend_only and not src_file_local.exists():
raise FileNotFoundError(f"File not found: {src_file_local}")
return f"/{path}"
# Shared asset handling
# Determine the file by which the asset is exposed.
frame = inspect.stack()[_stack_level]
calling_file = frame.filename
module = inspect.getmodule(frame[0])
assert module is not None
external = constants.Dirs.EXTERNAL_APP_ASSETS
src_file_shared = Path(calling_file).parent / path
if not src_file_shared.exists():
raise FileNotFoundError(f"File not found: {src_file_shared}")
caller_module_path = module.__name__.replace(".", "/")
subfolder = f"{caller_module_path}/{subfolder}" if subfolder else caller_module_path
# Symlink the asset to the app's external assets directory if running frontend.
if not backend_only:
# Create the asset folder in the currently compiling app.
asset_folder = Path.cwd() / assets / external / subfolder
asset_folder.mkdir(parents=True, exist_ok=True)
dst_file = asset_folder / path
if not dst_file.exists() and (
not dst_file.is_symlink() or dst_file.resolve() != src_file_shared.resolve()
):
dst_file.symlink_to(src_file_shared)
return f"/{external}/{subfolder}/{path}"

View File

@ -382,7 +382,7 @@ for theme_name in dir(Theme):
class CodeBlock(Component, MarkdownComponentMap): class CodeBlock(Component, MarkdownComponentMap):
"""A code block.""" """A code block."""
library = "react-syntax-highlighter@15.6.1" library = "react-syntax-highlighter@15.6.0"
tag = "PrismAsyncLight" tag = "PrismAsyncLight"

View File

@ -11,7 +11,6 @@ from reflex.components.radix.primitives.base import RadixPrimitiveComponent
from reflex.components.radix.themes.base import Theme from reflex.components.radix.themes.base import Theme
from reflex.components.radix.themes.layout.flex import Flex from reflex.components.radix.themes.layout.flex import Flex
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
from reflex.utils import console
from reflex.vars.base import Var from reflex.vars.base import Var
@ -140,19 +139,19 @@ class DrawerContent(DrawerComponent):
base_style.update(style) base_style.update(style)
return {"css": base_style} return {"css": base_style}
# Fired when the drawer content is opened. Deprecated. # Fired when the drawer content is opened.
on_open_auto_focus: EventHandler[no_args_event_spec] on_open_auto_focus: EventHandler[no_args_event_spec]
# Fired when the drawer content is closed. Deprecated. # Fired when the drawer content is closed.
on_close_auto_focus: EventHandler[no_args_event_spec] on_close_auto_focus: EventHandler[no_args_event_spec]
# Fired when the escape key is pressed. Deprecated. # Fired when the escape key is pressed.
on_escape_key_down: EventHandler[no_args_event_spec] on_escape_key_down: EventHandler[no_args_event_spec]
# Fired when the pointer is down outside the drawer content. Deprecated. # Fired when the pointer is down outside the drawer content.
on_pointer_down_outside: EventHandler[no_args_event_spec] on_pointer_down_outside: EventHandler[no_args_event_spec]
# Fired when interacting outside the drawer content. Deprecated. # Fired when interacting outside the drawer content.
on_interact_outside: EventHandler[no_args_event_spec] on_interact_outside: EventHandler[no_args_event_spec]
@classmethod @classmethod
@ -170,23 +169,6 @@ class DrawerContent(DrawerComponent):
Returns: Returns:
The drawer content. The drawer content.
""" """
deprecated_properties = [
"on_open_auto_focus",
"on_close_auto_focus",
"on_escape_key_down",
"on_pointer_down_outside",
"on_interact_outside",
]
for prop in deprecated_properties:
if prop in props:
console.deprecate(
feature_name="drawer content events",
reason=f"The `{prop}` event is deprecated and will be removed in 0.7.0.",
deprecation_version="0.6.3",
removal_version="0.7.0",
)
comp = super().create(*children, **props) comp = super().create(*children, **props)
return Theme.create(comp) return Theme.create(comp)

View File

@ -12,7 +12,9 @@ from reflex.vars.base import Var
from ..base import LiteralAccentColor, RadixThemesComponent from ..base import LiteralAccentColor, RadixThemesComponent
def on_value_change(value: Var[str | List[str]]) -> Tuple[Var[str | List[str]]]: def on_value_change(
value: Var[Union[str, List[str]]],
) -> Tuple[Var[Union[str, List[str]]]]:
"""Handle the on_value_change event. """Handle the on_value_change event.
Args: Args:

View File

@ -13,7 +13,9 @@ from reflex.vars.base import Var
from ..base import RadixThemesComponent from ..base import RadixThemesComponent
def on_value_change(value: Var[str | List[str]]) -> Tuple[Var[str | List[str]]]: ... def on_value_change(
value: Var[Union[str, List[str]]],
) -> Tuple[Var[Union[str, List[str]]]]: ...
class SegmentedControlRoot(RadixThemesComponent): class SegmentedControlRoot(RadixThemesComponent):
@overload @overload
@ -118,7 +120,10 @@ class SegmentedControlRoot(RadixThemesComponent):
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None, custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
on_blur: Optional[EventType[[], BASE_STATE]] = None, on_blur: Optional[EventType[[], BASE_STATE]] = None,
on_change: Optional[ on_change: Optional[
Union[EventType[[], BASE_STATE], EventType[[str | List[str]], BASE_STATE]] Union[
EventType[[], BASE_STATE],
EventType[[Union[str, List[str]]], BASE_STATE],
]
] = None, ] = None,
on_click: Optional[EventType[[], BASE_STATE]] = None, on_click: Optional[EventType[[], BASE_STATE]] = None,
on_context_menu: Optional[EventType[[], BASE_STATE]] = None, on_context_menu: Optional[EventType[[], BASE_STATE]] = None,

View File

@ -15,6 +15,8 @@ from reflex.utils.imports import ImportVar
from reflex.utils.serializers import serializer from reflex.utils.serializers import serializer
from reflex.vars import VarData from reflex.vars import VarData
from reflex.vars.base import LiteralVar, Var from reflex.vars.base import LiteralVar, Var
from reflex.vars.function import FunctionVar
from reflex.vars.object import ObjectVar
LiteralPosition = Literal[ LiteralPosition = Literal[
"top-left", "top-left",
@ -232,7 +234,9 @@ class Toaster(Component):
return [hook] return [hook]
@staticmethod @staticmethod
def send_toast(message: str = "", level: str | None = None, **props) -> EventSpec: def send_toast(
message: str | Var = "", level: str | None = None, **props
) -> EventSpec:
"""Send a toast message. """Send a toast message.
Args: Args:
@ -250,20 +254,27 @@ class Toaster(Component):
raise ValueError( raise ValueError(
"Toaster component must be created before sending a toast. (use `rx.toast.provider()`)" "Toaster component must be created before sending a toast. (use `rx.toast.provider()`)"
) )
toast_command = f"{toast_ref}.{level}" if level is not None else toast_ref
if message == "" and ("title" not in props or "description" not in props):
raise ValueError("Toast message or title or description must be provided.")
if props:
args = LiteralVar.create(ToastProps(component_name="rx.toast", **props)) # type: ignore
toast = f"{toast_command}(`{message}`, {str(args)})"
else:
toast = f"{toast_command}(`{message}`)"
toast_action = Var(_js_expr=toast) toast_command = (
return run_script(toast_action) ObjectVar.__getattr__(toast_ref.to(dict), level) if level else toast_ref
).to(FunctionVar)
if isinstance(message, Var):
props.setdefault("title", message)
message = ""
elif message == "" and "title" not in props and "description" not in props:
raise ValueError("Toast message or title or description must be provided.")
if props:
args = LiteralVar.create(ToastProps(component_name="rx.toast", **props)) # pyright: ignore [reportCallIssue, reportGeneralTypeIssues]
toast = toast_command.call(message, args)
else:
toast = toast_command.call(message)
return run_script(toast)
@staticmethod @staticmethod
def toast_info(message: str = "", **kwargs): def toast_info(message: str | Var = "", **kwargs):
"""Display an info toast message. """Display an info toast message.
Args: Args:
@ -276,7 +287,7 @@ class Toaster(Component):
return Toaster.send_toast(message, level="info", **kwargs) return Toaster.send_toast(message, level="info", **kwargs)
@staticmethod @staticmethod
def toast_warning(message: str = "", **kwargs): def toast_warning(message: str | Var = "", **kwargs):
"""Display a warning toast message. """Display a warning toast message.
Args: Args:
@ -289,7 +300,7 @@ class Toaster(Component):
return Toaster.send_toast(message, level="warning", **kwargs) return Toaster.send_toast(message, level="warning", **kwargs)
@staticmethod @staticmethod
def toast_error(message: str = "", **kwargs): def toast_error(message: str | Var = "", **kwargs):
"""Display an error toast message. """Display an error toast message.
Args: Args:
@ -302,7 +313,7 @@ class Toaster(Component):
return Toaster.send_toast(message, level="error", **kwargs) return Toaster.send_toast(message, level="error", **kwargs)
@staticmethod @staticmethod
def toast_success(message: str = "", **kwargs): def toast_success(message: str | Var = "", **kwargs):
"""Display a success toast message. """Display a success toast message.
Args: Args:

View File

@ -59,16 +59,16 @@ class Toaster(Component):
def add_hooks(self) -> list[Var | str]: ... def add_hooks(self) -> list[Var | str]: ...
@staticmethod @staticmethod
def send_toast( def send_toast(
message: str = "", level: str | None = None, **props message: str | Var = "", level: str | None = None, **props
) -> EventSpec: ... ) -> EventSpec: ...
@staticmethod @staticmethod
def toast_info(message: str = "", **kwargs): ... def toast_info(message: str | Var = "", **kwargs): ...
@staticmethod @staticmethod
def toast_warning(message: str = "", **kwargs): ... def toast_warning(message: str | Var = "", **kwargs): ...
@staticmethod @staticmethod
def toast_error(message: str = "", **kwargs): ... def toast_error(message: str | Var = "", **kwargs): ...
@staticmethod @staticmethod
def toast_success(message: str = "", **kwargs): ... def toast_success(message: str | Var = "", **kwargs): ...
@staticmethod @staticmethod
def toast_dismiss(id: Var | str | None = None): ... def toast_dismiss(id: Var | str | None = None): ...
@overload @overload
@ -176,7 +176,7 @@ class ToastNamespace(ComponentNamespace):
@staticmethod @staticmethod
def __call__( def __call__(
message: str = "", level: Optional[str] = None, **props message: Union[str, Var] = "", level: Optional[str] = None, **props
) -> "Optional[EventSpec]": ) -> "Optional[EventSpec]":
"""Send a toast message. """Send a toast message.

View File

@ -7,6 +7,7 @@ import importlib
import inspect import inspect
import os import os
import sys import sys
import threading
import urllib.parse import urllib.parse
from importlib.util import find_spec from importlib.util import find_spec
from pathlib import Path from pathlib import Path
@ -468,6 +469,14 @@ class PathExistsFlag:
ExistingPath = Annotated[Path, PathExistsFlag] ExistingPath = Annotated[Path, PathExistsFlag]
class PerformanceMode(enum.Enum):
"""Performance mode for the app."""
WARN = "warn"
RAISE = "raise"
OFF = "off"
class EnvironmentVariables: class EnvironmentVariables:
"""Environment variables class to instantiate environment variables.""" """Environment variables class to instantiate environment variables."""
@ -571,6 +580,15 @@ class EnvironmentVariables:
# Where to save screenshots when tests fail. # Where to save screenshots when tests fail.
SCREENSHOT_DIR: EnvVar[Optional[Path]] = env_var(None) SCREENSHOT_DIR: EnvVar[Optional[Path]] = env_var(None)
# Whether to check for outdated package versions.
REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True)
# In which performance mode to run the app.
REFLEX_PERF_MODE: EnvVar[Optional[PerformanceMode]] = env_var(PerformanceMode.WARN)
# The maximum size of the reflex state in kilobytes.
REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000)
# Whether to minify state names. Default to true in prod mode and false otherwise. # Whether to minify state names. Default to true in prod mode and false otherwise.
REFLEX_MINIFY_STATES: EnvVar[Optional[bool]] = env_var( REFLEX_MINIFY_STATES: EnvVar[Optional[bool]] = env_var(
default_factory=lambda: EnvironmentVariables.REFLEX_ENV_MODE.get() default_factory=lambda: EnvironmentVariables.REFLEX_ENV_MODE.get()
@ -844,6 +862,10 @@ def _get_config() -> Config:
return rxconfig.config return rxconfig.config
# Protect sys.path from concurrent modification
_config_lock = threading.RLock()
def get_config(reload: bool = False) -> Config: def get_config(reload: bool = False) -> Config:
"""Get the app config. """Get the app config.
@ -853,21 +875,26 @@ def get_config(reload: bool = False) -> Config:
Returns: Returns:
The app config. The app config.
""" """
# Remove any cached module when `reload` is requested. cached_rxconfig = sys.modules.get(constants.Config.MODULE, None)
if reload and constants.Config.MODULE in sys.modules: if cached_rxconfig is not None:
del sys.modules[constants.Config.MODULE] if reload:
# Remove any cached module when `reload` is requested.
del sys.modules[constants.Config.MODULE]
else:
return cached_rxconfig.config
sys_path = sys.path.copy() with _config_lock:
sys.path.clear() sys_path = sys.path.copy()
sys.path.append(os.getcwd())
try:
# Try to import the module with only the current directory in the path.
return _get_config()
except Exception:
# If the module import fails, try to import with the original sys.path.
sys.path.extend(sys_path)
return _get_config()
finally:
# Restore the original sys.path.
sys.path.clear() sys.path.clear()
sys.path.extend(sys_path) sys.path.append(os.getcwd())
try:
# Try to import the module with only the current directory in the path.
return _get_config()
except Exception:
# If the module import fails, try to import with the original sys.path.
sys.path.extend(sys_path)
return _get_config()
finally:
# Restore the original sys.path.
sys.path.clear()
sys.path.extend(sys_path)

View File

@ -97,6 +97,18 @@ class Templates(SimpleNamespace):
# The default template # The default template
DEFAULT = "blank" DEFAULT = "blank"
# The AI template
AI = "ai"
# The option for the user to choose a remote template.
CHOOSE_TEMPLATES = "choose-templates"
# The URL to find reflex templates.
REFLEX_TEMPLATES_URL = "https://reflex.dev/templates"
# Demo url for the default template.
DEFAULT_TEMPLATE_URL = "https://blank-template.reflex.run"
# The reflex.build frontend host # The reflex.build frontend host
REFLEX_BUILD_FRONTEND = "https://flexgen.reflex.run" REFLEX_BUILD_FRONTEND = "https://flexgen.reflex.run"

View File

@ -181,6 +181,18 @@ class EventActionsMixin:
event_actions={"debounce": delay_ms, **self.event_actions}, event_actions={"debounce": delay_ms, **self.event_actions},
) )
@property
def temporal(self):
"""Do not queue the event if the backend is down.
Returns:
New EventHandler-like with temporal set to True.
"""
return dataclasses.replace(
self,
event_actions={"temporal": True, **self.event_actions},
)
@dataclasses.dataclass( @dataclasses.dataclass(
init=True, init=True,
@ -1346,6 +1358,10 @@ def check_fn_match_arg_spec(
EventFnArgMismatch: Raised if the number of mandatory arguments do not match EventFnArgMismatch: Raised if the number of mandatory arguments do not match
""" """
user_args = inspect.getfullargspec(user_func).args user_args = inspect.getfullargspec(user_func).args
# Drop the first argument if it's a bound method
if inspect.ismethod(user_func) and user_func.__self__ is not None:
user_args = user_args[1:]
user_default_args = inspect.getfullargspec(user_func).defaults user_default_args = inspect.getfullargspec(user_func).defaults
number_of_user_args = len(user_args) - number_of_bound_args number_of_user_args = len(user_args) - number_of_bound_args
number_of_user_default_args = len(user_default_args) if user_default_args else 0 number_of_user_default_args = len(user_default_args) if user_default_args else 0

View File

@ -1,14 +1,15 @@
"""Helper functions for adding assets to the app.""" """Helper functions for adding assets to the app."""
import inspect
from pathlib import Path
from typing import Optional from typing import Optional
from reflex import constants from reflex import assets
from reflex.utils import console
def asset(relative_filename: str, subfolder: Optional[str] = None) -> str: def asset(relative_filename: str, subfolder: Optional[str] = None) -> str:
"""Add an asset to the app. """DEPRECATED: use `rx.asset` with `shared=True` instead.
Add an asset to the app.
Place the file next to your including python file. Place the file next to your including python file.
Copies the file to the app's external assets directory. Copies the file to the app's external assets directory.
@ -22,38 +23,15 @@ def asset(relative_filename: str, subfolder: Optional[str] = None) -> str:
relative_filename: The relative filename of the asset. relative_filename: The relative filename of the asset.
subfolder: The directory to place the asset in. subfolder: The directory to place the asset in.
Raises:
FileNotFoundError: If the file does not exist.
ValueError: If the module is None.
Returns: Returns:
The relative URL to the copied asset. The relative URL to the copied asset.
""" """
# Determine the file by which the asset is exposed. console.deprecate(
calling_file = inspect.stack()[1].filename feature_name="rx._x.asset",
module = inspect.getmodule(inspect.stack()[1][0]) reason="Use `rx.asset` with `shared=True` instead of `rx._x.asset`.",
if module is None: deprecation_version="0.6.6",
raise ValueError("Module is None") removal_version="0.7.0",
caller_module_path = module.__name__.replace(".", "/") )
return assets.asset(
subfolder = f"{caller_module_path}/{subfolder}" if subfolder else caller_module_path relative_filename, shared=True, subfolder=subfolder, _stack_level=2
)
src_file = Path(calling_file).parent / relative_filename
assets = constants.Dirs.APP_ASSETS
external = constants.Dirs.EXTERNAL_APP_ASSETS
if not src_file.exists():
raise FileNotFoundError(f"File not found: {src_file}")
# Create the asset folder in the currently compiling app.
asset_folder = Path.cwd() / assets / external / subfolder
asset_folder.mkdir(parents=True, exist_ok=True)
dst_file = asset_folder / relative_filename
if not dst_file.exists():
dst_file.symlink_to(src_file)
asset_url = f"/{external}/{subfolder}/{relative_filename}"
return asset_url

View File

@ -17,10 +17,10 @@ from reflex_cli.v2.deployments import check_version, hosting_cli
from reflex import constants from reflex import constants
from reflex.config import EnvironmentVariables, get_config from reflex.config import EnvironmentVariables, get_config
from reflex.custom_components.custom_components import custom_components_cli from reflex.custom_components.custom_components import custom_components_cli
from reflex.utils import console, redir, telemetry from reflex.utils import console, telemetry
# Disable typer+rich integration for help panels # Disable typer+rich integration for help panels
typer.core.rich = False # type: ignore typer.core.rich = None # type: ignore
# Create the app. # Create the app.
try: try:
@ -89,30 +89,8 @@ def _init(
# Set up the web project. # Set up the web project.
prerequisites.initialize_frontend_dependencies() prerequisites.initialize_frontend_dependencies()
# Integrate with reflex.build.
generation_hash = None
if ai:
if template is None:
# If AI is requested and no template specified, redirect the user to reflex.build.
generation_hash = redir.reflex_build_redirect()
elif prerequisites.is_generation_hash(template):
# Otherwise treat the template as a generation hash.
generation_hash = template
else:
console.error(
"Cannot use `--template` option with `--ai` option. Please remove `--template` option."
)
raise typer.Exit(2)
template = constants.Templates.DEFAULT
# Initialize the app. # Initialize the app.
template = prerequisites.initialize_app(app_name, template) template = prerequisites.initialize_app(app_name, template, ai)
# If a reflex.build generation hash is available, download the code and apply it to the main module.
if generation_hash:
prerequisites.initialize_main_module_index_from_generation(
app_name, generation_hash=generation_hash
)
# Initialize the .gitignore. # Initialize the .gitignore.
prerequisites.initialize_gitignore() prerequisites.initialize_gitignore()
@ -120,7 +98,7 @@ def _init(
# Initialize the requirements.txt. # Initialize the requirements.txt.
prerequisites.initialize_requirements_txt() prerequisites.initialize_requirements_txt()
template_msg = "" if not template else f" using the {template} template" template_msg = f" using the {template} template" if template else ""
# Finish initializing the app. # Finish initializing the app.
console.success(f"Initialized {app_name}{template_msg}") console.success(f"Initialized {app_name}{template_msg}")
@ -675,8 +653,7 @@ def deployv2(
project: Optional[str] = typer.Option( project: Optional[str] = typer.Option(
None, None,
"--project", "--project",
help="project to deploy to", help="project id to deploy to",
hidden=True,
), ),
token: Optional[str] = typer.Option( token: Optional[str] = typer.Option(
None, None,

View File

@ -43,7 +43,7 @@ from sqlalchemy.orm import DeclarativeBase
from typing_extensions import Self from typing_extensions import Self
from reflex import event from reflex import event
from reflex.config import get_config from reflex.config import PerformanceMode, get_config
from reflex.istate.data import RouterData from reflex.istate.data import RouterData
from reflex.istate.storage import ClientStorageBase from reflex.istate.storage import ClientStorageBase
from reflex.model import Model from reflex.model import Model
@ -62,6 +62,13 @@ try:
except ModuleNotFoundError: except ModuleNotFoundError:
import pydantic import pydantic
from pydantic import BaseModel as BaseModelV2
try:
from pydantic.v1 import BaseModel as BaseModelV1
except ModuleNotFoundError:
BaseModelV1 = BaseModelV2
import wrapt import wrapt
from redis.asyncio import Redis from redis.asyncio import Redis
from redis.exceptions import ResponseError from redis.exceptions import ResponseError
@ -90,6 +97,7 @@ from reflex.utils.exceptions import (
ReflexRuntimeError, ReflexRuntimeError,
SetUndefinedStateVarError, SetUndefinedStateVarError,
StateSchemaMismatchError, StateSchemaMismatchError,
StateTooLargeError,
) )
from reflex.utils.exec import is_testing_env from reflex.utils.exec import is_testing_env
from reflex.utils.serializers import serializer from reflex.utils.serializers import serializer
@ -110,10 +118,13 @@ Delta = Dict[str, Any]
var = computed_var var = computed_var
# If the state is this large, it's considered a performance issue. if EnvironmentVariables.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
TOO_LARGE_SERIALIZED_STATE = 100 * 1024 # 100kb # If the state is this large, it's considered a performance issue.
# Only warn about each state class size once. TOO_LARGE_SERIALIZED_STATE = (
_WARNED_ABOUT_STATE_SIZE: Set[str] = set() EnvironmentVariables.REFLEX_STATE_SIZE_LIMIT.get() * 1024
)
# Only warn about each state class size once.
_WARNED_ABOUT_STATE_SIZE: Set[str] = set()
# Errors caught during pickling of state # Errors caught during pickling of state
HANDLED_PICKLE_ERRORS = ( HANDLED_PICKLE_ERRORS = (
@ -1306,7 +1317,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
if parent_state is not None: if parent_state is not None:
return getattr(parent_state, name) return getattr(parent_state, name)
if isinstance(value, MutableProxy.__mutable_types__) and ( if MutableProxy._is_mutable_type(value) and (
name in super().__getattribute__("base_vars") or name in backend_vars name in super().__getattribute__("base_vars") or name in backend_vars
): ):
# track changes in mutable containers (list, dict, set, etc) # track changes in mutable containers (list, dict, set, etc)
@ -2155,7 +2166,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
state["__dict__"].pop(inherited_var_name, None) state["__dict__"].pop(inherited_var_name, None)
return state return state
def _warn_if_too_large( def _check_state_size(
self, self,
pickle_state_size: int, pickle_state_size: int,
): ):
@ -2163,6 +2174,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
Args: Args:
pickle_state_size: The size of the pickled state. pickle_state_size: The size of the pickled state.
Raises:
StateTooLargeError: If the state is too large.
""" """
state_full_name = self.get_full_name() state_full_name = self.get_full_name()
if ( if (
@ -2170,10 +2184,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
and pickle_state_size > TOO_LARGE_SERIALIZED_STATE and pickle_state_size > TOO_LARGE_SERIALIZED_STATE
and self.substates and self.substates
): ):
console.warn( msg = (
f"State {state_full_name} serializes to {pickle_state_size} bytes " f"State {state_full_name} serializes to {pickle_state_size} bytes "
"which may present performance issues. Consider reducing the size of this state." + "which may present performance issues. Consider reducing the size of this state."
) )
if EnvironmentVariables.REFLEX_PERF_MODE.get() == PerformanceMode.WARN:
console.warn(msg)
elif EnvironmentVariables.REFLEX_PERF_MODE.get() == PerformanceMode.RAISE:
raise StateTooLargeError(msg)
_WARNED_ABOUT_STATE_SIZE.add(state_full_name) _WARNED_ABOUT_STATE_SIZE.add(state_full_name)
@classmethod @classmethod
@ -2215,7 +2233,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
""" """
try: try:
pickle_state = pickle.dumps((self._to_schema(), self)) pickle_state = pickle.dumps((self._to_schema(), self))
self._warn_if_too_large(len(pickle_state)) if EnvironmentVariables.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
self._check_state_size(len(pickle_state))
return pickle_state return pickle_state
except HANDLED_PICKLE_ERRORS as og_pickle_error: except HANDLED_PICKLE_ERRORS as og_pickle_error:
error = ( error = (
@ -3606,7 +3625,16 @@ class MutableProxy(wrapt.ObjectProxy):
pydantic.BaseModel.__dict__ pydantic.BaseModel.__dict__
) )
__mutable_types__ = (list, dict, set, Base, DeclarativeBase) # These types will be wrapped in MutableProxy
__mutable_types__ = (
list,
dict,
set,
Base,
DeclarativeBase,
BaseModelV2,
BaseModelV1,
)
def __init__(self, wrapped: Any, state: BaseState, field_name: str): def __init__(self, wrapped: Any, state: BaseState, field_name: str):
"""Create a proxy for a mutable object that tracks changes. """Create a proxy for a mutable object that tracks changes.
@ -3646,6 +3674,18 @@ class MutableProxy(wrapt.ObjectProxy):
if wrapped is not None: if wrapped is not None:
return wrapped(*args, **(kwargs or {})) return wrapped(*args, **(kwargs or {}))
@classmethod
def _is_mutable_type(cls, value: Any) -> bool:
"""Check if a value is of a mutable type and should be wrapped.
Args:
value: The value to check.
Returns:
Whether the value is of a mutable type.
"""
return isinstance(value, cls.__mutable_types__)
def _wrap_recursive(self, value: Any) -> Any: def _wrap_recursive(self, value: Any) -> Any:
"""Wrap a value recursively if it is mutable. """Wrap a value recursively if it is mutable.
@ -3656,9 +3696,7 @@ class MutableProxy(wrapt.ObjectProxy):
The wrapped value. The wrapped value.
""" """
# Recursively wrap mutable types, but do not re-wrap MutableProxy instances. # Recursively wrap mutable types, but do not re-wrap MutableProxy instances.
if isinstance(value, self.__mutable_types__) and not isinstance( if self._is_mutable_type(value) and not isinstance(value, MutableProxy):
value, MutableProxy
):
return type(self)( return type(self)(
wrapped=value, wrapped=value,
state=self._self_state, state=self._self_state,
@ -3716,7 +3754,7 @@ class MutableProxy(wrapt.ObjectProxy):
self._wrap_recursive_decorator, self._wrap_recursive_decorator,
) )
if isinstance(value, self.__mutable_types__) and __name not in ( if self._is_mutable_type(value) and __name not in (
"__wrapped__", "__wrapped__",
"_self_state", "_self_state",
): ):

View File

@ -151,6 +151,10 @@ class InvalidPropValueError(ReflexError):
"""Raised when a prop value is invalid.""" """Raised when a prop value is invalid."""
class StateTooLargeError(ReflexError):
"""Raised when the state is too large to be serialized."""
class SystemPackageMissingError(ReflexError): class SystemPackageMissingError(ReflexError):
"""Raised when a system package is missing.""" """Raised when a system package is missing."""

View File

@ -34,7 +34,7 @@ from redis.asyncio import Redis
from reflex import constants, model from reflex import constants, model
from reflex.compiler import templates from reflex.compiler import templates
from reflex.config import Config, EnvironmentVariables, get_config from reflex.config import Config, EnvironmentVariables, get_config
from reflex.utils import console, net, path_ops, processes from reflex.utils import console, net, path_ops, processes, redir
from reflex.utils.exceptions import ( from reflex.utils.exceptions import (
GeneratedCodeHasNoFunctionDefs, GeneratedCodeHasNoFunctionDefs,
raise_system_package_missing_error, raise_system_package_missing_error,
@ -93,6 +93,8 @@ def check_latest_package_version(package_name: str):
Args: Args:
package_name: The name of the package. package_name: The name of the package.
""" """
if EnvironmentVariables.REFLEX_CHECK_LATEST_VERSION.get() is False:
return
try: try:
# Get the latest version from PyPI # Get the latest version from PyPI
current_version = importlib.metadata.version(package_name) current_version = importlib.metadata.version(package_name)
@ -1212,7 +1214,7 @@ def check_schema_up_to_date():
) )
def prompt_for_template(templates: list[Template]) -> str: def prompt_for_template_options(templates: list[Template]) -> str:
"""Prompt the user to specify a template. """Prompt the user to specify a template.
Args: Args:
@ -1224,9 +1226,14 @@ def prompt_for_template(templates: list[Template]) -> str:
# Show the user the URLs of each template to preview. # Show the user the URLs of each template to preview.
console.print("\nGet started with a template:") console.print("\nGet started with a template:")
def format_demo_url_str(url: str) -> str:
return f" ({url})" if url else ""
# Prompt the user to select a template. # Prompt the user to select a template.
id_to_name = { id_to_name = {
str(idx): f"{template.name} ({template.demo_url}) - {template.description}" str(
idx
): f"{template.name.replace('_', ' ').replace('-', ' ')}{format_demo_url_str(template.demo_url)} - {template.description}"
for idx, template in enumerate(templates) for idx, template in enumerate(templates)
} }
for id in range(len(id_to_name)): for id in range(len(id_to_name)):
@ -1381,15 +1388,119 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
shutil.rmtree(unzip_dir) shutil.rmtree(unzip_dir)
def initialize_app(app_name: str, template: str | None = None) -> str | None: def initialize_default_app(app_name: str):
"""Initialize the default app.
Args:
app_name: The name of the app.
"""
create_config(app_name)
initialize_app_directory(app_name)
def validate_and_create_app_using_remote_template(app_name, template, templates):
"""Validate and create an app using a remote template.
Args:
app_name: The name of the app.
template: The name of the template.
templates: The available templates.
Raises:
Exit: If the template is not found.
"""
# If user selects a template, it needs to exist.
if template in templates:
template_url = templates[template].code_url
else:
# Check if the template is a github repo.
if template.startswith("https://github.com"):
template_url = f"{template.strip('/').replace('.git', '')}/archive/main.zip"
else:
console.error(f"Template `{template}` not found.")
raise typer.Exit(1)
if template_url is None:
return
create_config_init_app_from_remote_template(
app_name=app_name, template_url=template_url
)
def generate_template_using_ai(template: str | None = None) -> str:
"""Generate a template using AI(Flexgen).
Args:
template: The name of the template.
Returns:
The generation hash.
Raises:
Exit: If the template and ai flags are used.
"""
if template is None:
# If AI is requested and no template specified, redirect the user to reflex.build.
return redir.reflex_build_redirect()
elif is_generation_hash(template):
# Otherwise treat the template as a generation hash.
return template
else:
console.error(
"Cannot use `--template` option with `--ai` option. Please remove `--template` option."
)
raise typer.Exit(2)
def fetch_remote_templates(
template: Optional[str] = None,
) -> tuple[str, dict[str, Template]]:
"""Fetch the available remote templates.
Args:
template: The name of the template.
Returns:
The selected template and the available templates.
Raises:
Exit: If the template is not valid or if the template is not specified.
"""
available_templates = {}
try:
# Get the available templates
available_templates = fetch_app_templates(constants.Reflex.VERSION)
except Exception as e:
console.warn("Failed to fetch templates. Falling back to default template.")
console.debug(f"Error while fetching templates: {e}")
template = constants.Templates.DEFAULT
if template == constants.Templates.DEFAULT:
return template, available_templates
if template in available_templates:
return template, available_templates
else:
if template is not None:
console.error(f"{template!r} is not a valid template name.")
console.print(
f"Go to the templates page ({constants.Templates.REFLEX_TEMPLATES_URL}) and copy the command to init with a template."
)
raise typer.Exit(0)
def initialize_app(
app_name: str, template: str | None = None, ai: bool = False
) -> str | None:
"""Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit. """Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit.
Args: Args:
app_name: The name of the app. app_name: The name of the app.
template: The name of the template to use. template: The name of the template to use.
ai: Whether to use AI to generate the template.
Raises:
Exit: If template is directly provided in the command flag and is invalid.
Returns: Returns:
The name of the template. The name of the template.
@ -1402,54 +1513,73 @@ def initialize_app(app_name: str, template: str | None = None) -> str | None:
telemetry.send("reinit") telemetry.send("reinit")
return return
generation_hash = None
if ai:
generation_hash = generate_template_using_ai(template)
template = constants.Templates.DEFAULT
templates: dict[str, Template] = {} templates: dict[str, Template] = {}
# Don't fetch app templates if the user directly asked for DEFAULT. # Don't fetch app templates if the user directly asked for DEFAULT.
if template is None or (template != constants.Templates.DEFAULT): if template is not None and (template not in (constants.Templates.DEFAULT,)):
try: template, templates = fetch_remote_templates(template)
# Get the available templates
templates = fetch_app_templates(constants.Reflex.VERSION) if template is None:
if template is None and len(templates) > 0: template = prompt_for_template_options(get_init_cli_prompt_options())
template = prompt_for_template(list(templates.values())) if template == constants.Templates.AI:
except Exception as e: generation_hash = generate_template_using_ai()
console.warn("Failed to fetch templates. Falling back to default template.") # change to the default to allow creation of default app
console.debug(f"Error while fetching templates: {e}") template = constants.Templates.DEFAULT
finally: elif template == constants.Templates.CHOOSE_TEMPLATES:
template = template or constants.Templates.DEFAULT template, templates = fetch_remote_templates()
# If the blank template is selected, create a blank app. # If the blank template is selected, create a blank app.
if template == constants.Templates.DEFAULT: if template in (constants.Templates.DEFAULT,):
# Default app creation behavior: a blank app. # Default app creation behavior: a blank app.
create_config(app_name) initialize_default_app(app_name)
initialize_app_directory(app_name)
else: else:
# Fetch App templates from the backend server. validate_and_create_app_using_remote_template(
console.debug(f"Available templates: {templates}") app_name=app_name, template=template, templates=templates
# If user selects a template, it needs to exist.
if template in templates:
template_url = templates[template].code_url
else:
# Check if the template is a github repo.
if template.startswith("https://github.com"):
template_url = (
f"{template.strip('/').replace('.git', '')}/archive/main.zip"
)
else:
console.error(f"Template `{template}` not found.")
raise typer.Exit(1)
if template_url is None:
return
create_config_init_app_from_remote_template(
app_name=app_name, template_url=template_url
) )
# If a reflex.build generation hash is available, download the code and apply it to the main module.
if generation_hash:
initialize_main_module_index_from_generation(
app_name, generation_hash=generation_hash
)
telemetry.send("init", template=template) telemetry.send("init", template=template)
return template return template
def get_init_cli_prompt_options() -> list[Template]:
"""Get the CLI options for initializing a Reflex app.
Returns:
The CLI options.
"""
return [
Template(
name=constants.Templates.DEFAULT,
description="A blank Reflex app.",
demo_url=constants.Templates.DEFAULT_TEMPLATE_URL,
code_url="",
),
Template(
name=constants.Templates.AI,
description="Generate a template using AI [Experimental]",
demo_url="",
code_url="",
),
Template(
name=constants.Templates.CHOOSE_TEMPLATES,
description="Choose an existing template.",
demo_url="",
code_url="",
),
]
def initialize_main_module_index_from_generation(app_name: str, generation_hash: str): def initialize_main_module_index_from_generation(app_name: str, generation_hash: str):
"""Overwrite the `index` function in the main module with reflex.build generated code. """Overwrite the `index` function in the main module with reflex.build generated code.

View File

@ -10,6 +10,18 @@ from .. import constants
from . import console from . import console
def open_browser(target_url: str) -> None:
"""Open a browser window to target_url.
Args:
target_url: The URL to open in the browser.
"""
if not webbrowser.open(target_url):
console.warn(
f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
)
def open_browser_and_wait( def open_browser_and_wait(
target_url: str, poll_url: str, interval: int = 2 target_url: str, poll_url: str, interval: int = 2
) -> httpx.Response: ) -> httpx.Response:
@ -23,10 +35,7 @@ def open_browser_and_wait(
Returns: Returns:
The response from the poll_url. The response from the poll_url.
""" """
if not webbrowser.open(target_url): open_browser(target_url)
console.warn(
f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
)
console.info("[b]Complete the workflow in the browser to continue.[/b]") console.info("[b]Complete the workflow in the browser to continue.[/b]")
while True: while True:
try: try:

View File

@ -270,6 +270,53 @@ def serialize_base(value: Base) -> dict:
} }
try:
from pydantic.v1 import BaseModel as BaseModelV1
@serializer(to=dict)
def serialize_base_model_v1(model: BaseModelV1) -> dict:
"""Serialize a pydantic v1 BaseModel instance.
Args:
model: The BaseModel to serialize.
Returns:
The serialized BaseModel.
"""
return model.dict()
from pydantic import BaseModel as BaseModelV2
if BaseModelV1 is not BaseModelV2:
@serializer(to=dict)
def serialize_base_model_v2(model: BaseModelV2) -> dict:
"""Serialize a pydantic v2 BaseModel instance.
Args:
model: The BaseModel to serialize.
Returns:
The serialized BaseModel.
"""
return model.model_dump()
except ImportError:
# Older pydantic v1 import
from pydantic import BaseModel as BaseModelV1
@serializer(to=dict)
def serialize_base_model_v1(model: BaseModelV1) -> dict:
"""Serialize a pydantic v1 BaseModel instance.
Args:
model: The BaseModel to serialize.
Returns:
The serialized BaseModel.
"""
return model.dict()
@serializer @serializer
def serialize_set(value: Set) -> list: def serialize_set(value: Set) -> list:
"""Serialize a set to a JSON serializable list. """Serialize a set to a JSON serializable list.

View File

@ -576,6 +576,10 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
return does_obj_satisfy_typed_dict(obj, cls) return does_obj_satisfy_typed_dict(obj, cls)
return isinstance(obj, dict) return isinstance(obj, dict)
# cls is a float
if cls is float:
return isinstance(obj, (float, int))
# cls is a simple class # cls is a simple class
return isinstance(obj, cls) return isinstance(obj, cls)

View File

@ -0,0 +1,94 @@
import shutil
from pathlib import Path
from typing import Generator
import pytest
import reflex as rx
import reflex.constants as constants
def test_shared_asset() -> None:
"""Test shared assets."""
# The asset function copies a file to the app's external assets directory.
asset = rx.asset(path="custom_script.js", shared=True, subfolder="subfolder")
assert asset == "/external/test_assets/subfolder/custom_script.js"
result_file = Path(
Path.cwd(), "assets/external/test_assets/subfolder/custom_script.js"
)
assert result_file.exists()
# Running a second time should not raise an error.
asset = rx.asset(path="custom_script.js", shared=True, subfolder="subfolder")
# Test the asset function without a subfolder.
asset = rx.asset(path="custom_script.js", shared=True)
assert asset == "/external/test_assets/custom_script.js"
result_file = Path(Path.cwd(), "assets/external/test_assets/custom_script.js")
assert result_file.exists()
# clean up
shutil.rmtree(Path.cwd() / "assets/external")
with pytest.raises(FileNotFoundError):
asset = rx.asset("non_existent_file.js")
# Nothing is done to assets when file does not exist.
assert not Path(Path.cwd() / "assets/external").exists()
def test_deprecated_x_asset(capsys) -> None:
"""Test that the deprecated asset function raises a warning.
Args:
capsys: Pytest fixture that captures stdout and stderr.
"""
assert rx.asset("custom_script.js", shared=True) == rx._x.asset("custom_script.js")
assert (
"DeprecationWarning: rx._x.asset has been deprecated in version 0.6.6"
in capsys.readouterr().out
)
@pytest.mark.parametrize(
"path,shared",
[
pytest.param("non_existing_file", True),
pytest.param("non_existing_file", False),
],
)
def test_invalid_assets(path: str, shared: bool) -> None:
"""Test that asset raises an error when the file does not exist.
Args:
path: The path to the asset.
shared: Whether the asset should be shared.
"""
with pytest.raises(FileNotFoundError):
_ = rx.asset(path, shared=shared)
@pytest.fixture
def custom_script_in_asset_dir() -> Generator[Path, None, None]:
"""Create a custom_script.js file in the app's assets directory.
Yields:
The path to the custom_script.js file.
"""
asset_dir = Path.cwd() / constants.Dirs.APP_ASSETS
asset_dir.mkdir(exist_ok=True)
path = asset_dir / "custom_script.js"
path.touch()
yield path
path.unlink()
def test_local_asset(custom_script_in_asset_dir: Path) -> None:
"""Test that no error is raised if shared is set and both files exist.
Args:
custom_script_in_asset_dir: Fixture that creates a custom_script.js file in the app's assets directory.
"""
asset = rx.asset("custom_script.js", shared=False)
assert asset == "/custom_script.js"

View File

@ -1,36 +0,0 @@
import shutil
from pathlib import Path
import pytest
import reflex as rx
def test_asset():
# Test the asset function.
# The asset function copies a file to the app's external assets directory.
asset = rx._x.asset("custom_script.js", "subfolder")
assert asset == "/external/test_assets/subfolder/custom_script.js"
result_file = Path(
Path.cwd(), "assets/external/test_assets/subfolder/custom_script.js"
)
assert result_file.exists()
# Running a second time should not raise an error.
asset = rx._x.asset("custom_script.js", "subfolder")
# Test the asset function without a subfolder.
asset = rx._x.asset("custom_script.js")
assert asset == "/external/test_assets/custom_script.js"
result_file = Path(Path.cwd(), "assets/external/test_assets/custom_script.js")
assert result_file.exists()
# clean up
shutil.rmtree(Path.cwd() / "assets/external")
with pytest.raises(FileNotFoundError):
asset = rx._x.asset("non_existent_file.js")
# Nothing is done to assets when file does not exist.
assert not Path(Path.cwd() / "assets/external").exists()

View File

@ -2,6 +2,7 @@ from typing import Callable, List
import pytest import pytest
import reflex as rx
from reflex.event import ( from reflex.event import (
Event, Event,
EventChain, EventChain,
@ -439,3 +440,17 @@ def test_event_var_data():
# Ensure chain carries _var_data # Ensure chain carries _var_data
chain_var = Var.create(EventChain(events=[S.s(S.x)], args_spec=_args_spec)) chain_var = Var.create(EventChain(events=[S.s(S.x)], args_spec=_args_spec))
assert chain_var._get_all_var_data() == S.x._get_all_var_data() assert chain_var._get_all_var_data() == S.x._get_all_var_data()
def test_event_bound_method() -> None:
class S(BaseState):
@event
def e(self, arg: str):
print(arg)
class Wrapper:
def get_handler(self, arg: str):
return S.e(arg)
w = Wrapper()
_ = rx.input(on_change=w.get_handler)

View File

@ -16,6 +16,8 @@ from unittest.mock import AsyncMock, Mock
import pytest import pytest
import pytest_asyncio import pytest_asyncio
from plotly.graph_objects import Figure from plotly.graph_objects import Figure
from pydantic import BaseModel as BaseModelV2
from pydantic.v1 import BaseModel as BaseModelV1
import reflex as rx import reflex as rx
import reflex.config import reflex.config
@ -3414,6 +3416,53 @@ def test_typed_state() -> None:
_ = TypedState(field="str") _ = TypedState(field="str")
class ModelV1(BaseModelV1):
"""A pydantic BaseModel v1."""
foo: str = "bar"
class ModelV2(BaseModelV2):
"""A pydantic BaseModel v2."""
foo: str = "bar"
@dataclasses.dataclass
class ModelDC:
"""A dataclass."""
foo: str = "bar"
class PydanticState(rx.State):
"""A state with pydantic BaseModel vars."""
v1: ModelV1 = ModelV1()
v2: ModelV2 = ModelV2()
dc: ModelDC = ModelDC()
def test_mutable_models():
"""Test that dataclass and pydantic BaseModel v1 and v2 use dep tracking."""
state = PydanticState()
assert isinstance(state.v1, MutableProxy)
state.v1.foo = "baz"
assert state.dirty_vars == {"v1"}
state.dirty_vars.clear()
assert isinstance(state.v2, MutableProxy)
state.v2.foo = "baz"
assert state.dirty_vars == {"v2"}
state.dirty_vars.clear()
# Not yet supported ENG-4083
# assert isinstance(state.dc, MutableProxy)
# state.dc.foo = "baz"
# assert state.dirty_vars == {"dc"}
# state.dirty_vars.clear()
def test_get_value(): def test_get_value():
class GetValueState(rx.State): class GetValueState(rx.State):
foo: str = "FOO" foo: str = "FOO"