Merge remote-tracking branch 'origin/main' into simon/hosting-cli-upgrades

This commit is contained in:
simon 2024-11-21 17:03:06 -08:00
commit f2c904ee72
36 changed files with 821 additions and 294 deletions

View File

@ -3,7 +3,7 @@ fail_fast: true
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.7.2
rev: v0.7.4
hooks:
- id: ruff-format
args: [reflex, tests]

264
poetry.lock generated
View File

@ -1350,8 +1350,8 @@ files = [
[package.dependencies]
numpy = [
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
{version = ">=1.23.2", markers = "python_version == \"3.11\""},
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
{version = ">=1.22.4", markers = "python_version < \"3.11\""},
]
python-dateutil = ">=2.8.2"
@ -1656,22 +1656,19 @@ files = [
[[package]]
name = "pydantic"
version = "2.9.2"
version = "2.10.1"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"},
{file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"},
{file = "pydantic-2.10.1-py3-none-any.whl", hash = "sha256:a8d20db84de64cf4a7d59e899c2caf0fe9d660c7cfc482528e7020d7dd189a7e"},
{file = "pydantic-2.10.1.tar.gz", hash = "sha256:a4daca2dc0aa429555e0656d6bf94873a7dc5f54ee42b1f5873d666fb3f35560"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
pydantic-core = "2.23.4"
typing-extensions = [
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
{version = ">=4.6.1", markers = "python_version < \"3.13\""},
]
pydantic-core = "2.27.1"
typing-extensions = ">=4.12.2"
[package.extras]
email = ["email-validator (>=2.0.0)"]
@ -1679,100 +1676,111 @@ timezone = ["tzdata"]
[[package]]
name = "pydantic-core"
version = "2.23.4"
version = "2.27.1"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"},
{file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"},
{file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"},
{file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"},
{file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"},
{file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"},
{file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"},
{file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"},
{file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"},
{file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"},
{file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"},
{file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"},
{file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"},
{file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"},
{file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"},
{file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"},
{file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"},
{file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"},
{file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"},
{file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"},
{file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"},
{file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"},
{file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"},
{file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"},
{file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"},
{file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"},
{file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"},
{file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"},
{file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"},
{file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"},
{file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"},
{file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"},
{file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"},
{file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"},
{file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"},
{file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"},
{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.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"},
{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.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"},
{file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"},
{file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"},
{file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"},
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"},
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"},
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"},
{file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"},
{file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"},
{file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"},
{file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"},
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"},
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"},
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"},
{file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"},
{file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"},
{file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"},
{file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"},
{file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"},
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"},
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"},
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"},
{file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"},
{file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"},
{file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"},
{file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"},
{file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"},
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"},
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"},
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"},
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"},
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"},
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"},
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"},
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"},
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"},
{file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"},
{file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"},
{file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"},
{file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"},
{file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"},
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"},
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"},
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"},
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"},
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"},
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"},
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"},
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"},
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"},
{file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"},
{file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"},
{file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"},
{file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"},
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"},
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"},
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"},
{file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"},
{file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"},
{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.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"},
{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]
@ -2289,29 +2297,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
version = "0.7.2"
version = "0.7.4"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.7.2-py3-none-linux_armv6l.whl", hash = "sha256:b73f873b5f52092e63ed540adefc3c36f1f803790ecf2590e1df8bf0a9f72cb8"},
{file = "ruff-0.7.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5b813ef26db1015953daf476202585512afd6a6862a02cde63f3bafb53d0b2d4"},
{file = "ruff-0.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:853277dbd9675810c6826dad7a428d52a11760744508340e66bf46f8be9701d9"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21aae53ab1490a52bf4e3bf520c10ce120987b047c494cacf4edad0ba0888da2"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc7e0fc6e0cb3168443eeadb6445285abaae75142ee22b2b72c27d790ab60ba"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd77877a4e43b3a98e5ef4715ba3862105e299af0c48942cc6d51ba3d97dc859"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e00163fb897d35523c70d71a46fbaa43bf7bf9af0f4534c53ea5b96b2e03397b"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3c54b538633482dc342e9b634d91168fe8cc56b30a4b4f99287f4e339103e88"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b792468e9804a204be221b14257566669d1db5c00d6bb335996e5cd7004ba80"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba53ed84ac19ae4bfb4ea4bf0172550a2285fa27fbb13e3746f04c80f7fa088"},
{file = "ruff-0.7.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b19fafe261bf741bca2764c14cbb4ee1819b67adb63ebc2db6401dcd652e3748"},
{file = "ruff-0.7.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28bd8220f4d8f79d590db9e2f6a0674f75ddbc3847277dd44ac1f8d30684b828"},
{file = "ruff-0.7.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9fd67094e77efbea932e62b5d2483006154794040abb3a5072e659096415ae1e"},
{file = "ruff-0.7.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:576305393998b7bd6c46018f8104ea3a9cb3fa7908c21d8580e3274a3b04b691"},
{file = "ruff-0.7.2-py3-none-win32.whl", hash = "sha256:fa993cfc9f0ff11187e82de874dfc3611df80852540331bc85c75809c93253a8"},
{file = "ruff-0.7.2-py3-none-win_amd64.whl", hash = "sha256:dd8800cbe0254e06b8fec585e97554047fb82c894973f7ff18558eee33d1cb88"},
{file = "ruff-0.7.2-py3-none-win_arm64.whl", hash = "sha256:bb8368cd45bba3f57bb29cbb8d64b4a33f8415d0149d2655c5c8539452ce7760"},
{file = "ruff-0.7.2.tar.gz", hash = "sha256:2b14e77293380e475b4e3a7a368e14549288ed2931fce259a6f99978669e844f"},
{file = "ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478"},
{file = "ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63"},
{file = "ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20"},
{file = "ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109"},
{file = "ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452"},
{file = "ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea"},
{file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7"},
{file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05"},
{file = "ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06"},
{file = "ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc"},
{file = "ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172"},
{file = "ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a"},
{file = "ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd"},
{file = "ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a"},
{file = "ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac"},
{file = "ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6"},
{file = "ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f"},
{file = "ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2"},
]
[[package]]
@ -2350,23 +2358,23 @@ websocket-client = ">=1.8,<2.0"
[[package]]
name = "setuptools"
version = "75.5.0"
version = "75.6.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.9"
files = [
{file = "setuptools-75.5.0-py3-none-any.whl", hash = "sha256:87cb777c3b96d638ca02031192d40390e0ad97737e27b6b4fa831bea86f2f829"},
{file = "setuptools-75.5.0.tar.gz", hash = "sha256:5c4ccb41111392671f02bb5f8436dfc5a9a7185e80500531b133f5775c4163ef"},
{file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"},
{file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"},
]
[package.extras]
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"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
enabler = ["pytest-enabler (>=2.2)"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.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]]
name = "shellingham"
@ -2773,13 +2781,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "uvicorn"
version = "0.32.0"
version = "0.32.1"
description = "The lightning-fast ASGI server."
optional = false
python-versions = ">=3.8"
files = [
{file = "uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82"},
{file = "uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e"},
{file = "uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e"},
{file = "uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175"},
]
[package.dependencies]
@ -2788,7 +2796,7 @@ h11 = ">=0.8"
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
[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]]
name = "virtualenv"
@ -3033,4 +3041,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = "^3.9"
content-hash = "600773827321e7a0fdb2fe7d6af650a039dff759ce6df9c51ca22f0d7df4c290"
content-hash = "a610d4c4bfd852f30e69ad2fbb288c2d9cbdf49e05b9d4936fe5e9b2a7cdefdb"

View File

@ -70,7 +70,7 @@ dill = ">=0.3.8"
toml = ">=0.10.2,<1.0"
pytest-asyncio = ">=0.24.0"
pytest-cov = ">=4.0.0,<7.0"
ruff = "0.7.2"
ruff = "0.7.4"
pandas = ">=2.1.1,<3.0"
pillow = ">=10.0.0,<12.0"
plotly = ">=5.13.0,<6.0"

View File

@ -705,6 +705,11 @@ export const useEventLoop = (
_e.stopPropagation();
}
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 throttle returns false, the events are not added to the queue.
if (!throttle(combined_name, event_actions.throttle)) {
@ -762,7 +767,7 @@ export const useEventLoop = (
window.onunhandledrejection = function (event) {
addEvents([
Event(`${exception_state_name}.handle_frontend_exception`, {
stack: event.reason.stack,
stack: event.reason?.stack,
component_stack: "",
}),
]);
@ -837,11 +842,20 @@ export const useEventLoop = (
}
};
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("routeChangeComplete", change_complete);
router.events.on("routeChangeError", change_error);
return () => {
router.events.off("routeChangeStart", change_start);
router.events.off("routeChangeComplete", change_complete);
router.events.off("routeChangeError", change_error);
};
}, [router]);

View File

@ -850,10 +850,9 @@ class App(MiddlewareMixin, LifespanMixin):
if self.theme is not None:
# If a theme component was provided, wrap the app with it
app_wrappers[(20, "Theme")] = self.theme
# Fix #2992 by removing the top-level appearance prop
self.theme.appearance = None
for route in self.unevaluated_pages:
console.debug(f"Evaluating page: {route}")
self._compile_page(route)
# Add the optional endpoints (_upload)
@ -1006,6 +1005,9 @@ class App(MiddlewareMixin, LifespanMixin):
compile_results.append(
compiler.compile_contexts(self.state, self.theme),
)
if self.theme is not None:
# Fix #2992 by removing the top-level appearance prop
self.theme.appearance = None
progress.advance(task)
# Compile the app root.

View File

@ -186,6 +186,23 @@ ComponentStyle = Dict[
ComponentChild = Union[types.PrimitiveType, Var, BaseComponent]
def satisfies_type_hint(obj: Any, type_hint: Any) -> bool:
"""Check if an object satisfies a type hint.
Args:
obj: The object to check.
type_hint: The type hint to check against.
Returns:
Whether the object satisfies the type hint.
"""
if isinstance(obj, LiteralVar):
return types._isinstance(obj._var_value, type_hint)
if isinstance(obj, Var):
return types._issubclass(obj._var_type, type_hint)
return types._isinstance(obj, type_hint)
class Component(BaseComponent, ABC):
"""A component with style, event trigger and other props."""
@ -460,8 +477,7 @@ class Component(BaseComponent, ABC):
)
) or (
# Else just check if the passed var type is valid.
not passed_types
and not types._issubclass(passed_type, expected_type, value)
not passed_types and not satisfies_type_hint(value, expected_type)
):
value_name = value._js_expr if isinstance(value, Var) else value

View File

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

View File

@ -5,6 +5,7 @@
from . import elements as elements
from .elements.forms import Button as Button
from .elements.forms import Datalist as Datalist
from .elements.forms import Fieldset as Fieldset
from .elements.forms import Form as Form
from .elements.forms import Input as Input
@ -18,6 +19,7 @@ from .elements.forms import Progress as Progress
from .elements.forms import Select as Select
from .elements.forms import Textarea as Textarea
from .elements.forms import button as button
from .elements.forms import datalist as datalist
from .elements.forms import fieldset as fieldset
from .elements.forms import form as form
from .elements.forms import input as input

View File

@ -7,6 +7,7 @@ from reflex.utils import lazy_loader
_MAPPING = {
"forms": [
"button",
"datalist",
"fieldset",
"form",
"input",

View File

@ -4,6 +4,7 @@
# ------------------------------------------------------
from .forms import Button as Button
from .forms import Datalist as Datalist
from .forms import Fieldset as Fieldset
from .forms import Form as Form
from .forms import Input as Input
@ -17,6 +18,7 @@ from .forms import Progress as Progress
from .forms import Select as Select
from .forms import Textarea as Textarea
from .forms import button as button
from .forms import datalist as datalist
from .forms import fieldset as fieldset
from .forms import form as form
from .forms import input as input
@ -226,6 +228,7 @@ from .typography import ul as ul
_MAPPING = {
"forms": [
"button",
"datalist",
"fieldset",
"form",
"input",

View File

@ -681,6 +681,7 @@ class Textarea(BaseHTML):
button = Button.create
datalist = Datalist.create
fieldset = Fieldset.create
form = Form.create
input = Input.create

View File

@ -1490,6 +1490,7 @@ class Textarea(BaseHTML):
...
button = Button.create
datalist = Datalist.create
fieldset = Fieldset.create
form = Form.create
input = Input.create

View File

@ -1,7 +1,8 @@
"""Moment component for humanized date rendering."""
import dataclasses
from typing import List, Optional
from datetime import date, datetime, time, timedelta
from typing import List, Optional, Union
from reflex.components.component import NoSSRComponent
from reflex.event import EventHandler, passthrough_event_spec
@ -19,7 +20,7 @@ class MomentDelta:
weeks: Optional[int] = dataclasses.field(default=None)
days: Optional[int] = dataclasses.field(default=None)
hours: Optional[int] = dataclasses.field(default=None)
minutess: Optional[int] = dataclasses.field(default=None)
minutes: Optional[int] = dataclasses.field(default=None)
seconds: Optional[int] = dataclasses.field(default=None)
milliseconds: Optional[int] = dataclasses.field(default=None)
@ -78,7 +79,7 @@ class Moment(NoSSRComponent):
duration: Var[str]
# The date to display (also work if passed as children).
date: Var[str]
date: Var[Union[str, datetime, date, time, timedelta]]
# Shows the duration (elapsed time) between now and the provided datetime.
duration_from_now: Var[bool]

View File

@ -4,6 +4,7 @@
# This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------
import dataclasses
from datetime import date, datetime, time, timedelta
from typing import Any, Dict, Optional, Union, overload
from reflex.components.component import NoSSRComponent
@ -20,7 +21,7 @@ class MomentDelta:
weeks: Optional[int]
days: Optional[int]
hours: Optional[int]
minutess: Optional[int]
minutes: Optional[int]
seconds: Optional[int]
milliseconds: Optional[int]
@ -46,7 +47,16 @@ class Moment(NoSSRComponent):
decimal: Optional[Union[Var[bool], bool]] = None,
unit: Optional[Union[Var[str], str]] = None,
duration: Optional[Union[Var[str], str]] = None,
date: Optional[Union[Var[str], str]] = None,
date: Optional[
Union[
Var[Union[date, datetime, str, time, timedelta]],
date,
datetime,
str,
time,
timedelta,
]
] = None,
duration_from_now: Optional[Union[Var[bool], bool]] = None,
unix: Optional[Union[Var[bool], bool]] = None,
local: Optional[Union[Var[bool], bool]] = None,

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.layout.flex import Flex
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
from reflex.utils import console
from reflex.vars.base import Var
@ -140,19 +139,19 @@ class DrawerContent(DrawerComponent):
base_style.update(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]
# Fired when the drawer content is closed. Deprecated.
# Fired when the drawer content is closed.
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]
# 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]
# Fired when interacting outside the drawer content. Deprecated.
# Fired when interacting outside the drawer content.
on_interact_outside: EventHandler[no_args_event_spec]
@classmethod
@ -170,23 +169,6 @@ class DrawerContent(DrawerComponent):
Returns:
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)
return Theme.create(comp)

View File

@ -12,7 +12,9 @@ from reflex.vars.base import Var
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.
Args:

View File

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

View File

@ -67,6 +67,9 @@ class TextFieldRoot(elements.Div, RadixThemesComponent):
# Value of the input
value: Var[Union[str, int, float]]
# References a datalist for suggested options
list: Var[Union[str, int, bool]]
# Fired when the value of the textarea changes.
on_change: EventHandler[input_event]

View File

@ -119,6 +119,7 @@ class TextFieldRoot(elements.Div, RadixThemesComponent):
required: Optional[Union[Var[bool], bool]] = None,
type: Optional[Union[Var[str], str]] = None,
value: Optional[Union[Var[Union[float, int, str]], float, int, str]] = None,
list: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
access_key: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
auto_capitalize: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
@ -206,6 +207,7 @@ class TextFieldRoot(elements.Div, RadixThemesComponent):
required: Indicates that the input is required
type: Specifies the type of input
value: Value of the input
list: References a datalist for suggested options
on_change: Fired when the value of the textarea changes.
on_focus: Fired when the textarea is focused.
on_blur: Fired when the textarea is blurred.
@ -454,6 +456,7 @@ class TextField(ComponentNamespace):
required: Optional[Union[Var[bool], bool]] = None,
type: Optional[Union[Var[str], str]] = None,
value: Optional[Union[Var[Union[float, int, str]], float, int, str]] = None,
list: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
access_key: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
auto_capitalize: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
@ -541,6 +544,7 @@ class TextField(ComponentNamespace):
required: Indicates that the input is required
type: Specifies the type of input
value: Value of the input
list: References a datalist for suggested options
on_change: Fired when the value of the textarea changes.
on_focus: Fired when the textarea is focused.
on_blur: Fired when the textarea is blurred.

View File

@ -15,6 +15,8 @@ from reflex.utils.imports import ImportVar
from reflex.utils.serializers import serializer
from reflex.vars import VarData
from reflex.vars.base import LiteralVar, Var
from reflex.vars.function import FunctionVar
from reflex.vars.object import ObjectVar
LiteralPosition = Literal[
"top-left",
@ -232,7 +234,9 @@ class Toaster(Component):
return [hook]
@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.
Args:
@ -250,20 +254,27 @@ class Toaster(Component):
raise ValueError(
"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)
return run_script(toast_action)
toast_command = (
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
def toast_info(message: str = "", **kwargs):
def toast_info(message: str | Var = "", **kwargs):
"""Display an info toast message.
Args:
@ -276,7 +287,7 @@ class Toaster(Component):
return Toaster.send_toast(message, level="info", **kwargs)
@staticmethod
def toast_warning(message: str = "", **kwargs):
def toast_warning(message: str | Var = "", **kwargs):
"""Display a warning toast message.
Args:
@ -289,7 +300,7 @@ class Toaster(Component):
return Toaster.send_toast(message, level="warning", **kwargs)
@staticmethod
def toast_error(message: str = "", **kwargs):
def toast_error(message: str | Var = "", **kwargs):
"""Display an error toast message.
Args:
@ -302,7 +313,7 @@ class Toaster(Component):
return Toaster.send_toast(message, level="error", **kwargs)
@staticmethod
def toast_success(message: str = "", **kwargs):
def toast_success(message: str | Var = "", **kwargs):
"""Display a success toast message.
Args:

View File

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

View File

@ -8,7 +8,9 @@ import importlib
import inspect
import os
import sys
import threading
import urllib.parse
from importlib.util import find_spec
from pathlib import Path
from typing import (
TYPE_CHECKING,
@ -452,6 +454,14 @@ class PathExistsFlag:
ExistingPath = Annotated[Path, PathExistsFlag]
class PerformanceMode(enum.Enum):
"""Performance mode for the app."""
WARN = "warn"
RAISE = "raise"
OFF = "off"
class EnvironmentVariables:
"""Environment variables class to instantiate environment variables."""
@ -545,6 +555,15 @@ class EnvironmentVariables:
# Where to save screenshots when tests fail.
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)
environment = EnvironmentVariables()
@ -798,6 +817,27 @@ class Config(Base):
self._replace_defaults(**kwargs)
def _get_config() -> Config:
"""Get the app config.
Returns:
The app config.
"""
# only import the module if it exists. If a module spec exists then
# the module exists.
spec = find_spec(constants.Config.MODULE)
if not spec:
# we need this condition to ensure that a ModuleNotFound error is not thrown when
# running unit/integration tests or during `reflex init`.
return Config(app_name="")
rxconfig = importlib.import_module(constants.Config.MODULE)
return rxconfig.config
# Protect sys.path from concurrent modification
_config_lock = threading.RLock()
def get_config(reload: bool = False) -> Config:
"""Get the app config.
@ -807,15 +847,26 @@ def get_config(reload: bool = False) -> Config:
Returns:
The app config.
"""
sys.path.insert(0, os.getcwd())
# only import the module if it exists. If a module spec exists then
# the module exists.
spec = importlib.util.find_spec(constants.Config.MODULE) # type: ignore
if not spec:
# we need this condition to ensure that a ModuleNotFound error is not thrown when
# running unit/integration tests.
return Config(app_name="")
rxconfig = importlib.import_module(constants.Config.MODULE)
if reload:
importlib.reload(rxconfig)
return rxconfig.config
cached_rxconfig = sys.modules.get(constants.Config.MODULE, None)
if cached_rxconfig is not None:
if reload:
# Remove any cached module when `reload` is requested.
del sys.modules[constants.Config.MODULE]
else:
return cached_rxconfig.config
with _config_lock:
sys_path = sys.path.copy()
sys.path.clear()
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
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
REFLEX_BUILD_FRONTEND = "https://flexgen.reflex.run"

View File

@ -184,15 +184,15 @@ class PackageJson(SimpleNamespace):
"json5": "2.2.3",
"next": "14.2.16",
"next-sitemap": "4.2.3",
"next-themes": "0.3.0",
"next-themes": "0.4.3",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-focus-lock": "2.13.2",
"socket.io-client": "4.8.1",
"universal-cookie": "7.2.1",
"universal-cookie": "7.2.2",
}
DEV_DEPENDENCIES = {
"autoprefixer": "10.4.20",
"postcss": "8.4.47",
"postcss": "8.4.49",
"postcss-import": "16.1.0",
}

View File

@ -7,7 +7,7 @@ class Tailwind(SimpleNamespace):
"""Tailwind constants."""
# The Tailwindcss version
VERSION = "tailwindcss@3.4.14"
VERSION = "tailwindcss@3.4.15"
# The Tailwind config.
CONFIG = "tailwind.config.js"
# Default Tailwind content paths

View File

@ -181,6 +181,18 @@ class EventActionsMixin:
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(
init=True,
@ -1346,6 +1358,10 @@ def check_fn_match_arg_spec(
EventFnArgMismatch: Raised if the number of mandatory arguments do not match
"""
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
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

View File

@ -17,10 +17,10 @@ from reflex import constants
from reflex.config import environment, get_config
from reflex.custom_components.custom_components import custom_components_cli
from reflex.state import reset_disk_state_manager
from reflex.utils import console, redir, telemetry
from reflex.utils import console, telemetry
# Disable typer+rich integration for help panels
typer.core.rich = False # type: ignore
typer.core.rich = None # type: ignore
# Create the app.
try:
@ -89,30 +89,8 @@ def _init(
# Set up the web project.
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.
template = prerequisites.initialize_app(app_name, template)
# 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
)
template = prerequisites.initialize_app(app_name, template, ai)
# Initialize the .gitignore.
prerequisites.initialize_gitignore()
@ -120,7 +98,7 @@ def _init(
# Initialize the requirements.txt.
prerequisites.initialize_requirements_txt()
template_msg = "" if template else f" using the {template} template"
template_msg = f" using the {template} template" if template else ""
# Finish initializing the app.
console.success(f"Initialized {app_name}{template_msg}")

View File

@ -43,7 +43,7 @@ from sqlalchemy.orm import DeclarativeBase
from typing_extensions import Self
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.storage import ClientStorageBase
from reflex.model import Model
@ -62,6 +62,13 @@ try:
except ModuleNotFoundError:
import pydantic
from pydantic import BaseModel as BaseModelV2
try:
from pydantic.v1 import BaseModel as BaseModelV1
except ModuleNotFoundError:
BaseModelV1 = BaseModelV2
import wrapt
from redis.asyncio import Redis
from redis.exceptions import ResponseError
@ -87,8 +94,10 @@ from reflex.utils.exceptions import (
ImmutableStateError,
InvalidStateManagerMode,
LockExpiredError,
ReflexRuntimeError,
SetUndefinedStateVarError,
StateSchemaMismatchError,
StateTooLargeError,
)
from reflex.utils.exec import is_testing_env
from reflex.utils.serializers import serializer
@ -109,10 +118,11 @@ Delta = Dict[str, Any]
var = computed_var
# If the state is this large, it's considered a performance issue.
TOO_LARGE_SERIALIZED_STATE = 100 * 1024 # 100kb
# Only warn about each state class size once.
_WARNED_ABOUT_STATE_SIZE: Set[str] = set()
if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
# If the state is this large, it's considered a performance issue.
TOO_LARGE_SERIALIZED_STATE = environment.REFLEX_STATE_SIZE_LIMIT.get() * 1024
# Only warn about each state class size once.
_WARNED_ABOUT_STATE_SIZE: Set[str] = set()
# Errors caught during pickling of state
HANDLED_PICKLE_ERRORS = (
@ -387,6 +397,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
"State classes should not be instantiated directly in a Reflex app. "
"See https://reflex.dev/docs/state/ for further information."
)
if type(self)._mixin:
raise ReflexRuntimeError(
f"{type(self).__name__} is a state mixin and cannot be instantiated directly."
)
kwargs["parent_state"] = parent_state
super().__init__()
for name, value in kwargs.items():
@ -1243,7 +1257,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
if parent_state is not None:
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
):
# track changes in mutable containers (list, dict, set, etc)
@ -2092,7 +2106,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
state["__dict__"].pop(inherited_var_name, None)
return state
def _warn_if_too_large(
def _check_state_size(
self,
pickle_state_size: int,
):
@ -2100,6 +2114,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
Args:
pickle_state_size: The size of the pickled state.
Raises:
StateTooLargeError: If the state is too large.
"""
state_full_name = self.get_full_name()
if (
@ -2107,10 +2124,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
and pickle_state_size > TOO_LARGE_SERIALIZED_STATE
and self.substates
):
console.warn(
msg = (
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 environment.REFLEX_PERF_MODE.get() == PerformanceMode.WARN:
console.warn(msg)
elif environment.REFLEX_PERF_MODE.get() == PerformanceMode.RAISE:
raise StateTooLargeError(msg)
_WARNED_ABOUT_STATE_SIZE.add(state_full_name)
@classmethod
@ -2152,7 +2173,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
"""
try:
pickle_state = pickle.dumps((self._to_schema(), self))
self._warn_if_too_large(len(pickle_state))
if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
self._check_state_size(len(pickle_state))
return pickle_state
except HANDLED_PICKLE_ERRORS as og_pickle_error:
error = (
@ -2367,6 +2389,23 @@ class ComponentState(State, mixin=True):
# The number of components created from this class.
_per_component_state_instance_count: ClassVar[int] = 0
def __init__(self, *args, **kwargs):
"""Do not allow direct initialization of the ComponentState.
Args:
*args: The args to pass to the State init method.
**kwargs: The kwargs to pass to the State init method.
Raises:
ReflexRuntimeError: If the ComponentState is initialized directly.
"""
if type(self)._mixin:
raise ReflexRuntimeError(
f"{ComponentState.__name__} {type(self).__name__} is not meant to be initialized directly. "
+ "Use the `create` method to create a new instance and access the state via the `State` attribute."
)
super().__init__(*args, **kwargs)
@classmethod
def __init_subclass__(cls, mixin: bool = True, **kwargs):
"""Overwrite mixin default to True.
@ -3526,7 +3565,16 @@ class MutableProxy(wrapt.ObjectProxy):
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):
"""Create a proxy for a mutable object that tracks changes.
@ -3566,6 +3614,18 @@ class MutableProxy(wrapt.ObjectProxy):
if wrapped is not None:
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:
"""Wrap a value recursively if it is mutable.
@ -3576,9 +3636,7 @@ class MutableProxy(wrapt.ObjectProxy):
The wrapped value.
"""
# Recursively wrap mutable types, but do not re-wrap MutableProxy instances.
if isinstance(value, self.__mutable_types__) and not isinstance(
value, MutableProxy
):
if self._is_mutable_type(value) and not isinstance(value, MutableProxy):
return type(self)(
wrapped=value,
state=self._self_state,
@ -3636,7 +3694,7 @@ class MutableProxy(wrapt.ObjectProxy):
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__",
"_self_state",
):

View File

@ -151,6 +151,10 @@ class InvalidPropValueError(ReflexError):
"""Raised when a prop value is invalid."""
class StateTooLargeError(ReflexError):
"""Raised when the state is too large to be serialized."""
class SystemPackageMissingError(ReflexError):
"""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.compiler import templates
from reflex.config import Config, environment, 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 (
GeneratedCodeHasNoFunctionDefs,
raise_system_package_missing_error,
@ -93,6 +93,8 @@ def check_latest_package_version(package_name: str):
Args:
package_name: The name of the package.
"""
if environment.REFLEX_CHECK_LATEST_VERSION.get() is False:
return
try:
# Get the latest version from PyPI
current_version = importlib.metadata.version(package_name)
@ -1209,7 +1211,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.
Args:
@ -1221,9 +1223,14 @@ def prompt_for_template(templates: list[Template]) -> str:
# Show the user the URLs of each template to preview.
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.
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 id in range(len(id_to_name)):
@ -1378,15 +1385,119 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
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.
Args:
app_name: The name of the app.
template: The name of the template to use.
Raises:
Exit: If template is directly provided in the command flag and is invalid.
ai: Whether to use AI to generate the template.
Returns:
The name of the template.
@ -1399,54 +1510,73 @@ def initialize_app(app_name: str, template: str | None = None) -> str | None:
telemetry.send("reinit")
return
generation_hash = None
if ai:
generation_hash = generate_template_using_ai(template)
template = constants.Templates.DEFAULT
templates: dict[str, Template] = {}
# Don't fetch app templates if the user directly asked for DEFAULT.
if template is None or (template != constants.Templates.DEFAULT):
try:
# Get the available templates
templates = fetch_app_templates(constants.Reflex.VERSION)
if template is None and len(templates) > 0:
template = prompt_for_template(list(templates.values()))
except Exception as e:
console.warn("Failed to fetch templates. Falling back to default template.")
console.debug(f"Error while fetching templates: {e}")
finally:
template = template or constants.Templates.DEFAULT
if template is not None and (template not in (constants.Templates.DEFAULT,)):
template, templates = fetch_remote_templates(template)
if template is None:
template = prompt_for_template_options(get_init_cli_prompt_options())
if template == constants.Templates.AI:
generation_hash = generate_template_using_ai()
# change to the default to allow creation of default app
template = constants.Templates.DEFAULT
elif template == constants.Templates.CHOOSE_TEMPLATES:
template, templates = fetch_remote_templates()
# 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.
create_config(app_name)
initialize_app_directory(app_name)
initialize_default_app(app_name)
else:
# Fetch App templates from the backend server.
console.debug(f"Available 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
validate_and_create_app_using_remote_template(
app_name=app_name, template=template, templates=templates
)
# 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)
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):
"""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
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(
target_url: str, poll_url: str, interval: int = 2
) -> httpx.Response:
@ -23,10 +35,7 @@ def open_browser_and_wait(
Returns:
The response from the poll_url.
"""
if not webbrowser.open(target_url):
console.warn(
f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
)
open_browser(target_url)
console.info("[b]Complete the workflow in the browser to continue.[/b]")
while True:
try:

View File

@ -263,7 +263,58 @@ def serialize_base(value: Base) -> dict:
Returns:
The serialized Base.
"""
return {k: v for k, v in value.dict().items() if not callable(v)}
from reflex.vars.base import Var
return {
k: v for k, v in value.dict().items() if isinstance(v, Var) or not callable(v)
}
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

View File

@ -14,9 +14,11 @@ from typing import (
Callable,
ClassVar,
Dict,
FrozenSet,
Iterable,
List,
Literal,
Mapping,
Optional,
Sequence,
Tuple,
@ -29,6 +31,7 @@ from typing import (
from typing import get_origin as get_origin_og
import sqlalchemy
from typing_extensions import is_typeddict
import reflex
from reflex.components.core.breakpoints import Breakpoints
@ -494,6 +497,14 @@ def _issubclass(cls: GenericType, cls_check: GenericType, instance: Any = None)
if isinstance(instance, Breakpoints):
return _breakpoints_satisfies_typing(cls_check, instance)
if isinstance(cls_check_base, tuple):
cls_check_base = tuple(
cls_check_one if not is_typeddict(cls_check_one) else dict
for cls_check_one in cls_check_base
)
if is_typeddict(cls_check_base):
cls_check_base = dict
# Check if the types match.
try:
return cls_check_base == Any or issubclass(cls_base, cls_check_base)
@ -503,6 +514,36 @@ def _issubclass(cls: GenericType, cls_check: GenericType, instance: Any = None)
raise TypeError(f"Invalid type for issubclass: {cls_base}") from te
def does_obj_satisfy_typed_dict(obj: Any, cls: GenericType) -> bool:
"""Check if an object satisfies a typed dict.
Args:
obj: The object to check.
cls: The typed dict to check against.
Returns:
Whether the object satisfies the typed dict.
"""
if not isinstance(obj, Mapping):
return False
key_names_to_values = get_type_hints(cls)
required_keys: FrozenSet[str] = getattr(cls, "__required_keys__", frozenset())
if not all(
isinstance(key, str)
and key in key_names_to_values
and _isinstance(value, key_names_to_values[key])
for key, value in obj.items()
):
return False
# TODO in 3.14: Implement https://peps.python.org/pep-0728/ if it's approved
# required keys are all present
return required_keys.issubset(required_keys)
def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
"""Check if an object is an instance of a class.
@ -529,6 +570,16 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
origin = get_origin(cls)
if origin is None:
# cls is a typed dict
if is_typeddict(cls):
if nested:
return does_obj_satisfy_typed_dict(obj, cls)
return isinstance(obj, dict)
# cls is a float
if cls is float:
return isinstance(obj, (float, int))
# cls is a simple class
return isinstance(obj, cls)
@ -553,7 +604,7 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
and len(obj) == len(args)
and all(_isinstance(item, arg) for item, arg in zip(obj, args))
)
if origin is dict:
if origin in (dict, Breakpoints):
return isinstance(obj, dict) and all(
_isinstance(key, args[0]) and _isinstance(value, args[1])
for key, value in obj.items()

View File

@ -1,7 +1,10 @@
"""Ensure that Components returned by ComponentState.create have independent State classes."""
import pytest
import reflex as rx
from reflex.components.base.bare import Bare
from reflex.utils.exceptions import ReflexRuntimeError
def test_component_state():
@ -40,3 +43,21 @@ def test_component_state():
assert len(cs2.children) == 1
assert cs2.children[0].render() == Bare.create("b").render()
assert cs2.id == "b"
def test_init_component_state() -> None:
"""Ensure that ComponentState subclasses cannot be instantiated directly."""
class CS(rx.ComponentState):
@classmethod
def get_component(cls, *children, **props):
return rx.el.div()
with pytest.raises(ReflexRuntimeError):
CS()
class SubCS(CS):
pass
with pytest.raises(ReflexRuntimeError):
SubCS()

View File

@ -2,6 +2,7 @@ from typing import Callable, List
import pytest
import reflex as rx
from reflex.event import (
Event,
EventChain,
@ -439,3 +440,17 @@ def test_event_var_data():
# Ensure chain carries _var_data
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()
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_asyncio
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.config
@ -43,7 +45,7 @@ from reflex.state import (
)
from reflex.testing import chdir
from reflex.utils import format, prerequisites, types
from reflex.utils.exceptions import SetUndefinedStateVarError
from reflex.utils.exceptions import ReflexRuntimeError, SetUndefinedStateVarError
from reflex.utils.format import json_dumps
from reflex.vars.base import Var, computed_var
from tests.units.states.mutation import MutableSQLAModel, MutableTestState
@ -3413,6 +3415,53 @@ def test_typed_state() -> None:
_ = 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():
class GetValueState(rx.State):
foo: str = "FOO"
@ -3441,3 +3490,19 @@ def test_get_value():
"bar": "foo",
}
}
def test_init_mixin() -> None:
"""Ensure that State mixins can not be instantiated directly."""
class Mixin(BaseState, mixin=True):
pass
with pytest.raises(ReflexRuntimeError):
Mixin()
class SubMixin(Mixin, mixin=True):
pass
with pytest.raises(ReflexRuntimeError):
SubMixin()