Merge branch 'main' into lendemor/error_computed_arg

This commit is contained in:
Lendemor 2025-02-05 02:23:46 +01:00
commit 83ee48887c
53 changed files with 2371 additions and 692 deletions

View File

@ -3,7 +3,7 @@ fail_fast: true
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.8.2
rev: v0.9.3
hooks:
- id: ruff-format
args: [reflex, tests]
@ -24,11 +24,12 @@ repos:
name: update-pyi-files
always_run: true
language: system
require_serial: true
description: 'Update pyi files as needed'
entry: python3 scripts/make_pyi.py
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.392
rev: v1.1.393
hooks:
- id: pyright
args: [reflex, tests]

View File

@ -239,7 +239,7 @@ Reflex se lanzó en diciembre de 2022 con el nombre de Pynecone.
- **Discusiones de GitHub**: Una excelente manera de hablar sobre las características que deseas agregar o las cosas que te resultan confusas o necesitan aclaración.
- **GitHub Issues**: Las incidencias son una forma excelente de informar de errores. Además, puedes intentar resolver un problema existente y enviar un PR.
Buscamos colaboradores, sin importar su nivel o experiencia. Para contribuir consulta [CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
Buscamos colaboradores, sin importar su nivel o experiencia. Para contribuir consulta [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
## Licencia

View File

@ -239,7 +239,7 @@ Reflex में हर सप्ताह नए रिलीज़ और फ
- **GitHub Discussions** (गिटहब चर्चाएँ): उन सुविधाओं के बारे में बात करने का एक शानदार तरीका जिन्हें आप जोड़ना चाहते हैं या ऐसी चीज़ें जो भ्रमित करने वाली हैं/स्पष्टीकरण की आवश्यकता है।
- **GitHub Issues** (गिटहब समस्याएं): ये [बग](https://github.com/reflex-dev/reflex/issues) की रिपोर्ट करने का एक शानदार तरीका है। इसके अतिरिक्त, आप किसी मौजूदा समस्या को हल करने का प्रयास कर सकते हैं और एक पीआर सबमिट कर सकते हैं।
हम सक्रिय रूप से योगदानकर्ताओं की तलाश कर रहे हैं, चाहे आपका कौशल स्तर या अनुभव कुछ भी हो।योगदान करने के लिए [CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) देखें।
हम सक्रिय रूप से योगदानकर्ताओं की तलाश कर रहे हैं, चाहे आपका कौशल स्तर या अनुभव कुछ भी हो।योगदान करने के लिए [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) देखें।
## हमारे सभी योगदानकर्ताओं का धन्यवाद:

View File

@ -222,7 +222,7 @@ app.add_page(index, title="DALL-E")
<div align="center">
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Component Library](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Gallery](https://reflex.dev/docs/gallery) &nbsp; | &nbsp; 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start) &nbsp;
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Component Library](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Templates](https://reflex.dev/templates/) &nbsp; | &nbsp; 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start) &nbsp;
</div>
@ -242,7 +242,7 @@ Reflex は毎週、新しいリリースや機能追加を行っています!
- **GitHub Discussions**: GitHub Discussions では、追加したい機能や、複雑で解明が必要な事柄についての議論に適している場所です。
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues)はバグの報告に適している場所です。また、課題を解決した PR のサブミットにチャレンジしていただくことも、可能です。
スキルや経験に関わらず、私たちはコントリビュータを積極的に探しています。コントリビュートするために、[CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)をご覧ください。
CONTスキルや経験に関わらず、私たちはコントリビュータを積極的に探しています。コントリビュートするために、[CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)をご覧ください。
## 私たちのコントリビュータに感謝!:

View File

@ -249,7 +249,7 @@ app.add_page(index, title="DALL-E")
- **بحث های GitHub**: راهی عالی برای صحبت در مورد ویژگی هایی که می خواهید اضافه کنید یا چیزهایی که گیج کننده هستند/نیاز به توضیح دارند.
- **قسمت مشکلات GitHub**: [قسمت مشکلات](https://github.com/reflex-dev/reflex/issues) یک راه عالی برای گزارش اشکال هستند. علاوه بر این، می توانید یک مشکل موجود را حل کنید و یک PR(pull request) ارسال کنید.
ما فعالانه به دنبال مشارکت کنندگان هستیم، فارغ از سطح مهارت یا تجربه شما. برای مشارکت [CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) را بررسی کنید.
ما فعالانه به دنبال مشارکت کنندگان هستیم، فارغ از سطح مهارت یا تجربه شما. برای مشارکت [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) را بررسی کنید.
## All Thanks To Our Contributors - با تشکر از همکاران ما:

View File

@ -200,7 +200,7 @@ Daha fazla sayfa ekleyerek çok sayfalı bir uygulama oluşturabilirsiniz.
<div align="center">
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Component Library](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Gallery](https://reflex.dev/docs/gallery) &nbsp; | &nbsp; 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy) &nbsp;
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Component Library](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Templates](https://reflex.dev/templates/) &nbsp; | &nbsp; 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy) &nbsp;
</div>
@ -229,7 +229,7 @@ Her boyuttaki katkıları memnuniyetle karşılıyoruz! Aşağıda Reflex toplul
- **GitHub Discussions**: Eklemek istediğiniz özellikler veya kafa karıştırıcı, açıklığa kavuşturulması gereken şeyler hakkında konuşmanın harika bir yolu.
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) hataları bildirmenin mükemmel bir yoludur. Ayrıca mevcut bir sorunu deneyip çözebilir ve bir PR (Pull Requests) gönderebilirsiniz.
Beceri düzeyiniz veya deneyiminiz ne olursa olsun aktif olarak katkıda bulunacak kişiler arıyoruz. Katkı sağlamak için katkı sağlama rehberimize bakabilirsiniz: [CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
Beceri düzeyiniz veya deneyiminiz ne olursa olsun aktif olarak katkıda bulunacak kişiler arıyoruz. Katkı sağlamak için katkı sağlama rehberimize bakabilirsiniz: [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
## Hepsi Katkıda Bulunanlar Sayesinde:

View File

@ -232,7 +232,7 @@ Bạn có thể tạo một ứng dụng nhiều trang bằng cách thêm trang.
<div align="center">
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Component Library](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Gallery](https://reflex.dev/docs/gallery) &nbsp; | &nbsp; 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start) &nbsp;
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Component Library](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Templates](https://reflex.dev/templates/) &nbsp; | &nbsp; 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start) &nbsp;
</div>
@ -254,7 +254,7 @@ Chúng tôi chào đón mọi đóng góp dù lớn hay nhỏ. Dưới đây là
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) là nơi tốt nhất để thông báo. Ngoài ra bạn có thể sửa chữa các vấn đề bằng cách tạo PR.
Chúng tôi luôn sẵn sàng tìm kiếm các contributor, bất kể kinh nghiệm. Để tham gia đóng góp, xin mời xem
[CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
[CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
## Xin cảm ơn các Contributors:

View File

@ -229,7 +229,7 @@ app.add_page(index, title="DALL-E")
<div align="center">
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Component Library](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Gallery](https://reflex.dev/docs/gallery) &nbsp; | &nbsp; 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start) &nbsp;
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Component Library](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Templates](https://reflex.dev/templates/) &nbsp; | &nbsp; 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start) &nbsp;
</div>
@ -251,7 +251,7 @@ Reflex 每周都有新功能和釋出新版本! 確保你按下 :star: 和 :eyes
- **GitHub Discussions**: 這是一個討論您想新增的功能或對於一些困惑/需要澄清事項的好方法。
- **GitHub Issues**: 在 [Issues](https://github.com/reflex-dev/reflex/issues) 頁面報告錯誤是一個絕佳的方式。此外,您也可以嘗試解決現有 Issue 並提交 PR。
我們積極尋找貢獻者,不論您的技能水平或經驗如何。要貢獻,請查看 [CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
我們積極尋找貢獻者,不論您的技能水平或經驗如何。要貢獻,請查看 [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
## 感謝所有貢獻者:

102
poetry.lock generated
View File

@ -164,15 +164,15 @@ virtualenv = ["virtualenv (>=20.0.35)"]
[[package]]
name = "certifi"
version = "2024.12.14"
version = "2025.1.31"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"},
{file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"},
{file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
{file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
]
[[package]]
@ -618,15 +618,15 @@ test = ["pytest (>=6)"]
[[package]]
name = "fastapi"
version = "0.115.7"
version = "0.115.8"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "fastapi-0.115.7-py3-none-any.whl", hash = "sha256:eb6a8c8bf7f26009e8147111ff15b5177a0e19bb4a45bc3486ab14804539d21e"},
{file = "fastapi-0.115.7.tar.gz", hash = "sha256:0f106da6c01d88a6786b3248fb4d7a940d071f6f488488898ad5d354b25ed015"},
{file = "fastapi-0.115.8-py3-none-any.whl", hash = "sha256:753a96dd7e036b34eeef8babdfcfe3f28ff79648f86551eb36bfc1b0bf4a8cbf"},
{file = "fastapi-0.115.8.tar.gz", hash = "sha256:0ce9111231720190473e222cdf0f07f7206ad7e53ea02beb1d2dc36e2f0741e9"},
]
[package.dependencies]
@ -1876,15 +1876,15 @@ files = [
[[package]]
name = "pyright"
version = "1.1.392.post0"
version = "1.1.393"
description = "Command line wrapper for pyright"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pyright-1.1.392.post0-py3-none-any.whl", hash = "sha256:252f84458a46fa2f0fd4e2f91fc74f50b9ca52c757062e93f6c250c0d8329eb2"},
{file = "pyright-1.1.392.post0.tar.gz", hash = "sha256:3b7f88de74a28dcfa90c7d90c782b6569a48c2be5f9d4add38472bdaac247ebd"},
{file = "pyright-1.1.393-py3-none-any.whl", hash = "sha256:8320629bb7a44ca90944ba599390162bf59307f3d9fb6e27da3b7011b8c17ae5"},
{file = "pyright-1.1.393.tar.gz", hash = "sha256:aeeb7ff4e0364775ef416a80111613f91a05c8e01e58ecfefc370ca0db7aed9c"},
]
[package.dependencies]
@ -1998,25 +1998,25 @@ histogram = ["pygal", "pygaljs", "setuptools"]
[[package]]
name = "pytest-codspeed"
version = "3.1.2"
version = "3.2.0"
description = "Pytest plugin to create CodSpeed benchmarks"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pytest_codspeed-3.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aed496f873670ce0ea8f980a7c1a2c6a08f415e0ebdf207bf651b2d922103374"},
{file = "pytest_codspeed-3.1.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee45b0b763f6b5fa5d74c7b91d694a9615561c428b320383660672f4471756e3"},
{file = "pytest_codspeed-3.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c84e591a7a0f67d45e2dc9fd05b276971a3aabcab7478fe43363ebefec1358f4"},
{file = "pytest_codspeed-3.1.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6ae6d094247156407770e6b517af70b98862dd59a3c31034aede11d5f71c32c"},
{file = "pytest_codspeed-3.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0f264991de5b5cdc118b96fc671386cca3f0f34e411482939bf2459dc599097"},
{file = "pytest_codspeed-3.1.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0695a4bcd5ff04e8379124dba5d9795ea5e0cadf38be7a0406432fc1467b555"},
{file = "pytest_codspeed-3.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc356c8dcaaa883af83310f397ac06c96fac9b8a1146e303d4b374b2cb46a18"},
{file = "pytest_codspeed-3.1.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc8a5d0366322a75cf562f7d8d672d28c1cf6948695c4dddca50331e08f6b3d5"},
{file = "pytest_codspeed-3.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c5fe7a19b72f54f217480b3b527102579547b1de9fe3acd9e66cb4629ff46c8"},
{file = "pytest_codspeed-3.1.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b67205755a665593f6521a98317d02a9d07d6fdc593f6634de2c94dea47a3055"},
{file = "pytest_codspeed-3.1.2-py3-none-any.whl", hash = "sha256:5e7ed0315e33496c5c07dba262b50303b8d0bc4c3d10bf1d422a41e70783f1cb"},
{file = "pytest_codspeed-3.1.2.tar.gz", hash = "sha256:09c1733af3aab35e94a621aa510f2d2114f65591e6f644c42ca3f67547edad4b"},
{file = "pytest_codspeed-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5165774424c7ab8db7e7acdb539763a0e5657996effefdf0664d7fd95158d34"},
{file = "pytest_codspeed-3.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bd55f92d772592c04a55209950c50880413ae46876e66bd349ef157075ca26c"},
{file = "pytest_codspeed-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf6f56067538f4892baa8d7ab5ef4e45bb59033be1ef18759a2c7fc55b32035"},
{file = "pytest_codspeed-3.2.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a687b05c3d145642061b45ea78e47e12f13ce510104d1a2cda00eee0e36f58"},
{file = "pytest_codspeed-3.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46a1afaaa1ac4c2ca5b0700d31ac46d80a27612961d031067d73c6ccbd8d3c2b"},
{file = "pytest_codspeed-3.2.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c48ce3af3dfa78413ed3d69d1924043aa1519048dbff46edccf8f35a25dab3c2"},
{file = "pytest_codspeed-3.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66692506d33453df48b36a84703448cb8b22953eea51f03fbb2eb758dc2bdc4f"},
{file = "pytest_codspeed-3.2.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:479774f80d0bdfafa16112700df4dbd31bf2a6757fac74795fd79c0a7b3c389b"},
{file = "pytest_codspeed-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:109f9f4dd1088019c3b3f887d003b7d65f98a7736ca1d457884f5aa293e8e81c"},
{file = "pytest_codspeed-3.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2f69a03b52c9bb041aec1b8ee54b7b6c37a6d0a948786effa4c71157765b6da"},
{file = "pytest_codspeed-3.2.0-py3-none-any.whl", hash = "sha256:54b5c2e986d6a28e7b0af11d610ea57bd5531cec8326abe486f1b55b09d91c39"},
{file = "pytest_codspeed-3.2.0.tar.gz", hash = "sha256:f9d1b1a3b2c69cdc0490a1e8b1ced44bffbd0e8e21d81a7160cfdd923f6e8155"},
]
[package.dependencies]
@ -2070,15 +2070,15 @@ dev = ["pre-commit", "pytest-asyncio", "tox"]
[[package]]
name = "pytest-playwright"
version = "0.6.2"
version = "0.7.0"
description = "A pytest wrapper with fixtures for Playwright to automate web browsers"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pytest_playwright-0.6.2-py3-none-any.whl", hash = "sha256:0eff73bebe497b0158befed91e2f5fe94cfa17181f8b3acf575beed84e7e9043"},
{file = "pytest_playwright-0.6.2.tar.gz", hash = "sha256:ff4054b19aa05df096ac6f74f0572591566aaf0f6d97f6cb9674db8a4d4ed06c"},
{file = "pytest_playwright-0.7.0-py3-none-any.whl", hash = "sha256:2516d0871fa606634bfe32afbcc0342d68da2dbff97fe3459849e9c428486da2"},
{file = "pytest_playwright-0.7.0.tar.gz", hash = "sha256:b3f2ea514bbead96d26376fac182f68dcd6571e7cb41680a89ff1673c05d60b6"},
]
[package.dependencies]
@ -2180,15 +2180,15 @@ docs = ["sphinx"]
[[package]]
name = "pytz"
version = "2024.2"
version = "2025.1"
description = "World timezone definitions, modern and historical"
optional = false
python-versions = "*"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"},
{file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"},
{file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"},
{file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"},
]
[[package]]
@ -2311,15 +2311,15 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"
[[package]]
name = "reflex-hosting-cli"
version = "0.1.33"
version = "0.1.34"
description = "Reflex Hosting CLI"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "reflex_hosting_cli-0.1.33-py3-none-any.whl", hash = "sha256:3fe72fc448a231c61de4ac646f42c936c70e91330f616a23aec658f905d53bc4"},
{file = "reflex_hosting_cli-0.1.33.tar.gz", hash = "sha256:81c4a896b106eea99f1cab53ea23a6e19802592ce0468cc38d93d440bc95263a"},
{file = "reflex_hosting_cli-0.1.34-py3-none-any.whl", hash = "sha256:eabc4dc7bf68e022a9388614c1a35b5ab36b01021df063d0c3356eda0e245264"},
{file = "reflex_hosting_cli-0.1.34.tar.gz", hash = "sha256:07be37fda6dcede0a5d4bc1fd1786d9a3df5ad4e49dc1b6ba335418563cfecec"},
]
[package.dependencies]
@ -2410,31 +2410,31 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
version = "0.8.2"
version = "0.9.3"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "ruff-0.8.2-py3-none-linux_armv6l.whl", hash = "sha256:c49ab4da37e7c457105aadfd2725e24305ff9bc908487a9bf8d548c6dad8bb3d"},
{file = "ruff-0.8.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ec016beb69ac16be416c435828be702ee694c0d722505f9c1f35e1b9c0cc1bf5"},
{file = "ruff-0.8.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f05cdf8d050b30e2ba55c9b09330b51f9f97d36d4673213679b965d25a785f3c"},
{file = "ruff-0.8.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60f578c11feb1d3d257b2fb043ddb47501ab4816e7e221fbb0077f0d5d4e7b6f"},
{file = "ruff-0.8.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbd5cf9b0ae8f30eebc7b360171bd50f59ab29d39f06a670b3e4501a36ba5897"},
{file = "ruff-0.8.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b402ddee3d777683de60ff76da801fa7e5e8a71038f57ee53e903afbcefdaa58"},
{file = "ruff-0.8.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:705832cd7d85605cb7858d8a13d75993c8f3ef1397b0831289109e953d833d29"},
{file = "ruff-0.8.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32096b41aaf7a5cc095fa45b4167b890e4c8d3fd217603f3634c92a541de7248"},
{file = "ruff-0.8.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e769083da9439508833cfc7c23e351e1809e67f47c50248250ce1ac52c21fb93"},
{file = "ruff-0.8.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fe716592ae8a376c2673fdfc1f5c0c193a6d0411f90a496863c99cd9e2ae25d"},
{file = "ruff-0.8.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:81c148825277e737493242b44c5388a300584d73d5774defa9245aaef55448b0"},
{file = "ruff-0.8.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d261d7850c8367704874847d95febc698a950bf061c9475d4a8b7689adc4f7fa"},
{file = "ruff-0.8.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1ca4e3a87496dc07d2427b7dd7ffa88a1e597c28dad65ae6433ecb9f2e4f022f"},
{file = "ruff-0.8.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:729850feed82ef2440aa27946ab39c18cb4a8889c1128a6d589ffa028ddcfc22"},
{file = "ruff-0.8.2-py3-none-win32.whl", hash = "sha256:ac42caaa0411d6a7d9594363294416e0e48fc1279e1b0e948391695db2b3d5b1"},
{file = "ruff-0.8.2-py3-none-win_amd64.whl", hash = "sha256:2aae99ec70abf43372612a838d97bfe77d45146254568d94926e8ed5bbb409ea"},
{file = "ruff-0.8.2-py3-none-win_arm64.whl", hash = "sha256:fb88e2a506b70cfbc2de6fae6681c4f944f7dd5f2fe87233a7233d888bad73e8"},
{file = "ruff-0.8.2.tar.gz", hash = "sha256:b84f4f414dda8ac7f75075c1fa0b905ac0ff25361f42e6d5da681a465e0f78e5"},
{file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"},
{file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"},
{file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"},
{file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"},
{file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"},
{file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"},
{file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"},
]
[[package]]
@ -3183,4 +3183,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.10, <4.0"
content-hash = "822150bcbf41e5cbb61da0a059b41d8971e3c6c974c8af4be7ef55126648aea1"
content-hash = "3b7e6e6e872c68f951f191d85a7d76fe1dd86caf32e2143a53a3152a3686fc7f"

View File

@ -41,7 +41,7 @@ wrapt = [
{ version = ">=1.11.0,<2.0", python = "<3.11" },
]
packaging = ">=23.1,<25.0"
reflex-hosting-cli = ">=0.1.29,<2.0"
reflex-hosting-cli = ">=0.1.29"
charset-normalizer = ">=3.3.2,<4.0"
wheel = ">=0.42.0,<1.0"
build = ">=1.0.3,<2.0"
@ -61,7 +61,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.8.2"
ruff = "0.9.3"
pandas = ">=2.1.1,<3.0"
pillow = ">=10.0.0,<12.0"
plotly = ">=5.13.0,<6.0"
@ -88,7 +88,7 @@ target-version = "py310"
output-format = "concise"
lint.isort.split-on-trailing-comma = false
lint.select = ["ANN001","B", "C4", "D", "E", "ERA", "F", "FURB", "I", "N", "PERF", "PGH", "PTH", "RUF", "SIM", "T", "TRY", "W"]
lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012", "TRY0"]
lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF008", "RUF012", "TRY0"]
lint.pydocstyle.convention = "google"
[tool.ruff.lint.per-file-ignores]

View File

@ -38,13 +38,13 @@ export default function MyApp({ Component, pageProps }) {
}, []);
return (
<ThemeProvider defaultTheme={ defaultColorMode } attribute="class">
<AppWrap>
<StateProvider>
<EventLoopProvider>
<Component {...pageProps} />
</EventLoopProvider>
</StateProvider>
</AppWrap>
<StateProvider>
<EventLoopProvider>
<AppWrap>
<Component {...pageProps} />
</AppWrap>
</EventLoopProvider>
</StateProvider>
</ThemeProvider>
);
}

View File

@ -60,7 +60,7 @@
{# Args: #}
{# component: component dictionary #}
{% macro render_iterable_tag(component) %}
<>{ {%- if component.iterable_type == 'dict' -%}Object.entries({{- component.iterable_state }}){%- else -%}{{- component.iterable_state }}{%- endif -%}.map(({{ component.arg_name }}, {{ component.arg_index }}) => (
<>{ {{ component.iterable_state }}.map(({{ component.arg_name }}, {{ component.arg_index }}) => (
{% for child in component.children %}
{{ render(child) }}
{% endfor %}

View File

@ -227,8 +227,8 @@ export const applyEvent = async (event, socket) => {
a.href = eval?.(
event.payload.url.replace(
"getBackendURL(env.UPLOAD)",
`"${getBackendURL(env.UPLOAD)}"`
)
`"${getBackendURL(env.UPLOAD)}"`,
),
);
}
a.download = event.payload.filename;
@ -341,7 +341,7 @@ export const applyRestEvent = async (event, socket) => {
event.payload.files,
event.payload.upload_id,
event.payload.on_upload_progress,
socket
socket,
);
return false;
}
@ -408,7 +408,7 @@ export const connect = async (
dispatch,
transports,
setConnectErrors,
client_storage = {}
client_storage = {},
) => {
// Get backend URL object from the endpoint.
const endpoint = getBackendURL(EVENTURL);
@ -499,7 +499,7 @@ export const uploadFiles = async (
files,
upload_id,
on_upload_progress,
socket
socket,
) => {
// return if there's no file to upload
if (files === undefined || files.length === 0) {
@ -604,7 +604,7 @@ export const Event = (
name,
payload = {},
event_actions = {},
handler = null
handler = null,
) => {
return { name, payload, handler, event_actions };
};
@ -631,7 +631,7 @@ export const hydrateClientStorage = (client_storage) => {
for (const state_key in client_storage.local_storage) {
const options = client_storage.local_storage[state_key];
const local_storage_value = localStorage.getItem(
options.name || state_key
options.name || state_key,
);
if (local_storage_value !== null) {
client_storage_values[state_key] = local_storage_value;
@ -642,7 +642,7 @@ export const hydrateClientStorage = (client_storage) => {
for (const state_key in client_storage.session_storage) {
const session_options = client_storage.session_storage[state_key];
const session_storage_value = sessionStorage.getItem(
session_options.name || state_key
session_options.name || state_key,
);
if (session_storage_value != null) {
client_storage_values[state_key] = session_storage_value;
@ -667,7 +667,7 @@ export const hydrateClientStorage = (client_storage) => {
const applyClientStorageDelta = (client_storage, delta) => {
// find the main state and check for is_hydrated
const unqualified_states = Object.keys(delta).filter(
(key) => key.split(".").length === 1
(key) => key.split(".").length === 1,
);
if (unqualified_states.length === 1) {
const main_state = delta[unqualified_states[0]];
@ -701,7 +701,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
const session_options = client_storage.session_storage[state_key];
sessionStorage.setItem(
session_options.name || state_key,
delta[substate][key]
delta[substate][key],
);
}
}
@ -721,7 +721,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
export const useEventLoop = (
dispatch,
initial_events = () => [],
client_storage = {}
client_storage = {},
) => {
const socket = useRef(null);
const router = useRouter();
@ -735,7 +735,7 @@ export const useEventLoop = (
event_actions = events.reduce(
(acc, e) => ({ ...acc, ...e.event_actions }),
event_actions ?? {}
event_actions ?? {},
);
const _e = args.filter((o) => o?.preventDefault !== undefined)[0];
@ -763,7 +763,7 @@ export const useEventLoop = (
debounce(
combined_name,
() => queueEvents(events, socket),
event_actions.debounce
event_actions.debounce,
);
} else {
queueEvents(events, socket);
@ -782,7 +782,7 @@ export const useEventLoop = (
query,
asPath,
}))(router),
}))
})),
);
sentHydrate.current = true;
}
@ -817,13 +817,9 @@ export const useEventLoop = (
};
}, []);
// Main event loop.
// Handle socket connect/disconnect.
useEffect(() => {
// Skip if the router is not ready.
if (!router.isReady) {
return;
}
// only use websockets if state is present
// only use websockets if state is present and backend is not disabled (reflex cloud).
if (Object.keys(initialState).length > 1 && !isBackendDisabled()) {
// Initialize the websocket connection.
if (!socket.current) {
@ -832,16 +828,31 @@ export const useEventLoop = (
dispatch,
["websocket"],
setConnectErrors,
client_storage
client_storage,
);
}
(async () => {
// Process all outstanding events.
while (event_queue.length > 0 && !event_processing) {
await processEvent(socket.current);
}
})();
}
// Cleanup function.
return () => {
if (socket.current) {
socket.current.disconnect();
}
};
}, []);
// Main event loop.
useEffect(() => {
// Skip if the router is not ready.
if (!router.isReady || isBackendDisabled()) {
return;
}
(async () => {
// Process all outstanding events.
while (event_queue.length > 0 && !event_processing) {
await processEvent(socket.current);
}
})();
});
// localStorage event handling
@ -865,7 +876,7 @@ export const useEventLoop = (
vars[storage_to_state_map[e.key]] = e.newValue;
const event = Event(
`${state_name}.reflex___state____update_vars_internal_state.update_vars_internal`,
{ vars: vars }
{ vars: vars },
);
addEvents([event], e);
}
@ -958,7 +969,7 @@ export const getRefValues = (refs) => {
return refs.map((ref) =>
ref.current
? ref.current.value || ref.current.getAttribute("aria-valuenow")
: null
: null,
);
};

View File

@ -54,6 +54,7 @@ from reflex.compiler.compiler import ExecutorSafeFunctions, compile_theme
from reflex.components.base.app_wrap import AppWrap
from reflex.components.base.error_boundary import ErrorBoundary
from reflex.components.base.fragment import Fragment
from reflex.components.base.strict_mode import StrictMode
from reflex.components.component import (
Component,
ComponentStyle,
@ -69,6 +70,7 @@ from reflex.components.core.client_side_routing import (
Default404Page,
wait_for_client_redirect,
)
from reflex.components.core.sticky import sticky
from reflex.components.core.upload import Upload, get_upload_dir
from reflex.components.radix import themes
from reflex.config import environment, get_config
@ -150,7 +152,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
position="top-center",
id="backend_error",
style={"width": "500px"},
) # pyright: ignore [reportReturnType]
)
else:
error_message.insert(0, "An error occurred.")
return window_alert("\n".join(error_message))
@ -874,6 +876,15 @@ class App(MiddlewareMixin, LifespanMixin):
continue
self._pages[k] = self._add_error_boundary_to_component(component)
def _setup_sticky_badge(self):
"""Add the sticky badge to the app."""
for k, component in self._pages.items():
# Would be nice to share single sticky_badge across all pages, but
# it bungles the StatefulComponent compile step.
sticky_badge = sticky()
sticky_badge._add_style_recursive({})
self._pages[k] = Fragment.create(sticky_badge, component)
def _apply_decorated_pages(self):
"""Add @rx.page decorated pages to the app.
@ -908,11 +919,17 @@ class App(MiddlewareMixin, LifespanMixin):
if not var._cache:
continue
deps = var._deps(objclass=state)
for dep in deps:
if dep not in state.vars and dep not in state.backend_vars:
raise exceptions.VarDependencyError(
f"ComputedVar {var._js_expr} on state {state.__name__} has an invalid dependency {dep}"
)
for state_name, dep_set in deps.items():
state_cls = (
state.get_root_state().get_class_substate(state_name)
if state_name != state.get_full_name()
else state
)
for dep in dep_set:
if dep not in state_cls.vars and dep not in state_cls.backend_vars:
raise exceptions.VarDependencyError(
f"ComputedVar {var._js_expr} on state {state.__name__} has an invalid dependency {state_name}.{dep}"
)
for substate in state.class_subclasses:
self._validate_var_dependencies(substate)
@ -950,21 +967,23 @@ class App(MiddlewareMixin, LifespanMixin):
# If a theme component was provided, wrap the app with it
app_wrappers[(20, "Theme")] = self.theme
# Get the env mode.
config = get_config()
if config.react_strict_mode:
app_wrappers[(200, "StrictMode")] = StrictMode.create()
should_compile = self._should_compile()
for route in self._unevaluated_pages:
console.debug(f"Evaluating page: {route}")
self._compile_page(route, save_page=should_compile)
# Add the optional endpoints (_upload)
self._add_optional_endpoints()
if not should_compile:
return
for route in self._unevaluated_pages:
console.debug(f"Evaluating page: {route}")
self._compile_page(route, save_page=should_compile)
self._validate_var_dependencies()
self._setup_overlay_component()
self._setup_error_boundary()
# Add the optional endpoints (_upload)
self._add_optional_endpoints()
return
# Create a progress bar.
progress = Progress(
@ -974,18 +993,32 @@ class App(MiddlewareMixin, LifespanMixin):
)
# try to be somewhat accurate - but still not 100%
adhoc_steps_without_executor = 6
adhoc_steps_without_executor = 7
fixed_pages_within_executor = 5
progress.start()
task = progress.add_task(
f"[{get_compilation_time()}] Compiling:",
total=len(self._pages)
+ (len(self._unevaluated_pages) * 2)
+ fixed_pages_within_executor
+ adhoc_steps_without_executor,
)
# Get the env mode.
config = get_config()
for route in self._unevaluated_pages:
console.debug(f"Evaluating page: {route}")
self._compile_page(route, save_page=should_compile)
progress.advance(task)
# Add the optional endpoints (_upload)
self._add_optional_endpoints()
self._validate_var_dependencies()
self._setup_overlay_component()
self._setup_error_boundary()
if config.show_built_with_reflex:
self._setup_sticky_badge()
progress.advance(task)
# Store the compile results.
compile_results = []
@ -1293,7 +1326,7 @@ class App(MiddlewareMixin, LifespanMixin):
):
raise ValueError(
f"Provided custom {handler_domain} exception handler `{_fn_name}` has the wrong argument order."
f"Expected `{required_arg}` as the {required_arg_index+1} argument but got `{list(arg_annotations.keys())[required_arg_index]}`"
f"Expected `{required_arg}` as the {required_arg_index + 1} argument but got `{list(arg_annotations.keys())[required_arg_index]}`"
)
if not issubclass(arg_annotations[required_arg], Exception):

View File

@ -239,11 +239,19 @@ def _compile_components(
component_renders.append(component_render)
imports = utils.merge_imports(imports, component_imports)
dynamic_imports = {
comp_import: None
for comp_render in component_renders
if "dynamic_imports" in comp_render
for comp_import in comp_render["dynamic_imports"]
}
# Compile the components page.
return (
templates.COMPONENTS.render(
imports=utils.compile_imports(imports),
components=component_renders,
dynamic_imports=dynamic_imports,
),
imports,
)

View File

@ -2,12 +2,15 @@
from __future__ import annotations
import asyncio
import concurrent.futures
import traceback
from datetime import datetime
from pathlib import Path
from typing import Any, Callable, Dict, Optional, Type, Union
from urllib.parse import urlparse
from reflex.utils.exec import is_in_app_harness
from reflex.utils.prerequisites import get_web_dir
from reflex.vars.base import Var
@ -33,7 +36,7 @@ from reflex.components.base import (
)
from reflex.components.component import Component, ComponentStyle, CustomComponent
from reflex.istate.storage import Cookie, LocalStorage, SessionStorage
from reflex.state import BaseState
from reflex.state import BaseState, _resolve_delta
from reflex.style import Style
from reflex.utils import console, format, imports, path_ops
from reflex.utils.imports import ImportVar, ParsedImportDict
@ -177,7 +180,24 @@ def compile_state(state: Type[BaseState]) -> dict:
initial_state = state(_reflex_internal_init=True).dict(
initial=True, include_computed=False
)
return initial_state
try:
_ = asyncio.get_running_loop()
except RuntimeError:
pass
else:
if is_in_app_harness():
# Playwright tests already have an event loop running, so we can't use asyncio.run.
with concurrent.futures.ThreadPoolExecutor() as pool:
resolved_initial_state = pool.submit(
asyncio.run, _resolve_delta(initial_state)
).result()
console.warn(
f"Had to get initial state in a thread 🤮 {resolved_initial_state}",
)
return resolved_initial_state
# Normally the compile runs before any event loop starts, we asyncio.run is available for calling.
return asyncio.run(_resolve_delta(initial_state))
def _compile_client_storage_field(
@ -300,6 +320,7 @@ def compile_custom_component(
"render": render.render(),
"hooks": render._get_all_hooks(),
"custom_code": render._get_all_custom_code(),
"dynamic_imports": render._get_all_dynamic_imports(),
},
imports,
)

View File

@ -0,0 +1,10 @@
"""Module for the StrictMode component."""
from reflex.components.component import Component
class StrictMode(Component):
"""A React strict mode component to enable strict mode for its children."""
library = "react"
tag = "StrictMode"

View File

@ -0,0 +1,57 @@
"""Stub file for reflex/components/base/strict_mode.py"""
# ------------------- DO NOT EDIT ----------------------
# This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------
from typing import Any, Dict, Optional, Union, overload
from reflex.components.component import Component
from reflex.event import BASE_STATE, EventType
from reflex.style import Style
from reflex.vars.base import Var
class StrictMode(Component):
@overload
@classmethod
def create( # type: ignore
cls,
*children,
style: Optional[Style] = None,
key: Optional[Any] = None,
id: Optional[Any] = None,
class_name: Optional[Any] = None,
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
on_blur: Optional[EventType[[], BASE_STATE]] = None,
on_click: Optional[EventType[[], BASE_STATE]] = None,
on_context_menu: Optional[EventType[[], BASE_STATE]] = None,
on_double_click: Optional[EventType[[], BASE_STATE]] = None,
on_focus: Optional[EventType[[], BASE_STATE]] = None,
on_mount: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_down: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_move: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_out: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_over: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_up: Optional[EventType[[], BASE_STATE]] = None,
on_scroll: Optional[EventType[[], BASE_STATE]] = None,
on_unmount: Optional[EventType[[], BASE_STATE]] = None,
**props,
) -> "StrictMode":
"""Create the component.
Args:
*children: The children of the component.
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
class_name: The class name for the component.
autofocus: Whether the component should take the focus once the page is loaded
custom_attrs: custom attribute
**props: The props of the component.
Returns:
The component.
"""
...

View File

@ -623,8 +623,7 @@ class Component(BaseComponent, ABC):
if props is None:
# Add component props to the tag.
props = {
attr[:-1] if attr.endswith("_") else attr: getattr(self, attr)
for attr in self.get_props()
attr.removesuffix("_"): getattr(self, attr) for attr in self.get_props()
}
# Add ref to element if `id` is not None.

View File

@ -54,6 +54,8 @@ class Foreach(Component):
TypeError: If the render function is a ComponentState.
UntypedVarError: If the iterable is of type Any without a type annotation.
"""
from reflex.vars.object import ObjectVar
iterable = LiteralVar.create(iterable)
if iterable._var_type == Any:
raise ForeachVarError(
@ -70,6 +72,9 @@ class Foreach(Component):
"Using a ComponentState as `render_fn` inside `rx.foreach` is not supported yet."
)
if isinstance(iterable, ObjectVar):
iterable = iterable.entries()
component = cls(
iterable=iterable,
render_fn=render_fn,

View File

@ -0,0 +1,160 @@
"""Components for displaying the Reflex sticky logo."""
from reflex.components.component import ComponentNamespace
from reflex.components.core.colors import color
from reflex.components.core.cond import color_mode_cond, cond
from reflex.components.core.responsive import tablet_and_desktop
from reflex.components.el.elements.inline import A
from reflex.components.el.elements.media import Path, Rect, Svg
from reflex.components.radix.themes.typography.text import Text
from reflex.experimental.client_state import ClientStateVar
from reflex.style import Style
from reflex.vars.base import Var, VarData
class StickyLogo(Svg):
"""A simple Reflex logo SVG with only the letter R."""
@classmethod
def create(cls):
"""Create the simple Reflex logo SVG.
Returns:
The simple Reflex logo SVG.
"""
return super().create(
Rect.create(width="16", height="16", rx="2", fill="#6E56CF"),
Path.create(d="M10 9V13H12V9H10Z", fill="white"),
Path.create(d="M4 3V13H6V9H10V7H6V5H10V7H12V3H4Z", fill="white"),
width="16",
height="16",
viewBox="0 0 16 16",
xmlns="http://www.w3.org/2000/svg",
)
def add_style(self):
"""Add the style to the component.
Returns:
The style of the component.
"""
return Style(
{
"fill": "white",
}
)
class StickyLabel(Text):
"""A label that displays the Reflex sticky."""
@classmethod
def create(cls):
"""Create the sticky label.
Returns:
The sticky label.
"""
return super().create("Built with Reflex")
def add_style(self):
"""Add the style to the component.
Returns:
The style of the component.
"""
return Style(
{
"color": color("slate", 1),
"font_weight": "600",
"font_family": "'Instrument Sans', sans-serif",
"font_size": "0.875rem",
"line_height": "1rem",
"letter_spacing": "-0.00656rem",
}
)
class StickyBadge(A):
"""A badge that displays the Reflex sticky logo."""
@classmethod
def create(cls):
"""Create the sticky badge.
Returns:
The sticky badge.
"""
return super().create(
StickyLogo.create(),
tablet_and_desktop(StickyLabel.create()),
href="https://reflex.dev",
target="_blank",
width="auto",
padding="0.375rem",
align="center",
text_align="center",
)
def add_style(self):
"""Add the style to the component.
Returns:
The style of the component.
"""
is_localhost_cs = ClientStateVar.create(
"is_localhost",
default=True,
global_ref=False,
)
localhost_hostnames = Var.create(
["localhost", "127.0.0.1", "[::1]"]
).guess_type()
is_localhost_expr = localhost_hostnames.contains(
Var("window.location.hostname", _var_type=str).guess_type(),
)
check_is_localhost = Var(
f"useEffect(({is_localhost_cs}) => {is_localhost_cs.set}({is_localhost_expr}), [])",
_var_data=VarData(
imports={"react": "useEffect"},
),
)
is_localhost = is_localhost_cs.value._replace(
merge_var_data=VarData.merge(
check_is_localhost._get_all_var_data(),
VarData(hooks={str(check_is_localhost): None}),
),
)
return Style(
{
"position": "fixed",
"bottom": "1rem",
"right": "1rem",
# Do not show the badge on localhost.
"display": cond(is_localhost, "none", "flex"),
"flex-direction": "row",
"gap": "0.375rem",
"align-items": "center",
"width": "auto",
"border-radius": "0.5rem",
"color": color_mode_cond("#E5E7EB", "#27282B"),
"border": color_mode_cond("1px solid #27282B", "1px solid #E5E7EB"),
"background-color": color_mode_cond("#151618", "#FCFCFD"),
"padding": "0.375rem",
"transition": "background-color 0.2s ease-in-out",
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
"z-index": "9998",
"cursor": "pointer",
},
)
class StickyNamespace(ComponentNamespace):
"""Sticky components namespace."""
__call__ = staticmethod(StickyBadge.create)
label = staticmethod(StickyLabel.create)
logo = staticmethod(StickyLogo.create)
sticky = StickyNamespace()

View File

@ -0,0 +1,449 @@
"""Stub file for reflex/components/core/sticky.py"""
# ------------------- DO NOT EDIT ----------------------
# This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------
from typing import Any, Dict, Literal, Optional, Union, overload
from reflex.components.component import ComponentNamespace
from reflex.components.core.breakpoints import Breakpoints
from reflex.components.el.elements.inline import A
from reflex.components.el.elements.media import Svg
from reflex.components.radix.themes.typography.text import Text
from reflex.event import BASE_STATE, EventType
from reflex.style import Style
from reflex.vars.base import Var
class StickyLogo(Svg):
@overload
@classmethod
def create( # type: ignore
cls,
*children,
width: Optional[Union[Var[Union[int, str]], int, str]] = None,
height: Optional[Union[Var[Union[int, str]], int, str]] = None,
xmlns: Optional[Union[Var[str], 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]
] = None,
content_editable: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
context_menu: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
dir: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
draggable: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
enter_key_hint: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
hidden: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
input_mode: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
item_prop: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
role: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
slot: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
spell_check: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
tab_index: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
title: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
style: Optional[Style] = None,
key: Optional[Any] = None,
id: Optional[Any] = None,
class_name: Optional[Any] = None,
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
on_blur: Optional[EventType[[], BASE_STATE]] = None,
on_click: Optional[EventType[[], BASE_STATE]] = None,
on_context_menu: Optional[EventType[[], BASE_STATE]] = None,
on_double_click: Optional[EventType[[], BASE_STATE]] = None,
on_focus: Optional[EventType[[], BASE_STATE]] = None,
on_mount: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_down: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_move: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_out: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_over: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_up: Optional[EventType[[], BASE_STATE]] = None,
on_scroll: Optional[EventType[[], BASE_STATE]] = None,
on_unmount: Optional[EventType[[], BASE_STATE]] = None,
**props,
) -> "StickyLogo":
"""Create the simple Reflex logo SVG.
Returns:
The simple Reflex logo SVG.
"""
...
def add_style(self): ...
class StickyLabel(Text):
@overload
@classmethod
def create( # type: ignore
cls,
*children,
as_child: Optional[Union[Var[bool], bool]] = None,
as_: Optional[
Union[
Literal[
"abbr",
"b",
"cite",
"del",
"div",
"em",
"i",
"ins",
"kbd",
"label",
"mark",
"p",
"s",
"samp",
"span",
"sub",
"sup",
"u",
],
Var[
Literal[
"abbr",
"b",
"cite",
"del",
"div",
"em",
"i",
"ins",
"kbd",
"label",
"mark",
"p",
"s",
"samp",
"span",
"sub",
"sup",
"u",
]
],
]
] = None,
size: Optional[
Union[
Breakpoints[str, Literal["1", "2", "3", "4", "5", "6", "7", "8", "9"]],
Literal["1", "2", "3", "4", "5", "6", "7", "8", "9"],
Var[
Union[
Breakpoints[
str, Literal["1", "2", "3", "4", "5", "6", "7", "8", "9"]
],
Literal["1", "2", "3", "4", "5", "6", "7", "8", "9"],
]
],
]
] = None,
weight: Optional[
Union[
Breakpoints[str, Literal["bold", "light", "medium", "regular"]],
Literal["bold", "light", "medium", "regular"],
Var[
Union[
Breakpoints[str, Literal["bold", "light", "medium", "regular"]],
Literal["bold", "light", "medium", "regular"],
]
],
]
] = None,
align: Optional[
Union[
Breakpoints[str, Literal["center", "left", "right"]],
Literal["center", "left", "right"],
Var[
Union[
Breakpoints[str, Literal["center", "left", "right"]],
Literal["center", "left", "right"],
]
],
]
] = None,
trim: Optional[
Union[
Breakpoints[str, Literal["both", "end", "normal", "start"]],
Literal["both", "end", "normal", "start"],
Var[
Union[
Breakpoints[str, Literal["both", "end", "normal", "start"]],
Literal["both", "end", "normal", "start"],
]
],
]
] = None,
color_scheme: Optional[
Union[
Literal[
"amber",
"blue",
"bronze",
"brown",
"crimson",
"cyan",
"gold",
"grass",
"gray",
"green",
"indigo",
"iris",
"jade",
"lime",
"mint",
"orange",
"pink",
"plum",
"purple",
"red",
"ruby",
"sky",
"teal",
"tomato",
"violet",
"yellow",
],
Var[
Literal[
"amber",
"blue",
"bronze",
"brown",
"crimson",
"cyan",
"gold",
"grass",
"gray",
"green",
"indigo",
"iris",
"jade",
"lime",
"mint",
"orange",
"pink",
"plum",
"purple",
"red",
"ruby",
"sky",
"teal",
"tomato",
"violet",
"yellow",
]
],
]
] = None,
high_contrast: Optional[Union[Var[bool], bool]] = 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]
] = None,
content_editable: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
context_menu: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
dir: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
draggable: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
enter_key_hint: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
hidden: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
input_mode: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
item_prop: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
role: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
slot: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
spell_check: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
tab_index: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
title: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
style: Optional[Style] = None,
key: Optional[Any] = None,
id: Optional[Any] = None,
class_name: Optional[Any] = None,
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
on_blur: Optional[EventType[[], BASE_STATE]] = None,
on_click: Optional[EventType[[], BASE_STATE]] = None,
on_context_menu: Optional[EventType[[], BASE_STATE]] = None,
on_double_click: Optional[EventType[[], BASE_STATE]] = None,
on_focus: Optional[EventType[[], BASE_STATE]] = None,
on_mount: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_down: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_move: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_out: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_over: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_up: Optional[EventType[[], BASE_STATE]] = None,
on_scroll: Optional[EventType[[], BASE_STATE]] = None,
on_unmount: Optional[EventType[[], BASE_STATE]] = None,
**props,
) -> "StickyLabel":
"""Create the sticky label.
Returns:
The sticky label.
"""
...
def add_style(self): ...
class StickyBadge(A):
@overload
@classmethod
def create( # type: ignore
cls,
*children,
download: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
href: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
href_lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
media: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
ping: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
referrer_policy: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
rel: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
shape: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
target: 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]
] = None,
content_editable: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
context_menu: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
dir: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
draggable: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
enter_key_hint: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
hidden: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
input_mode: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
item_prop: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
role: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
slot: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
spell_check: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
tab_index: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
title: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
style: Optional[Style] = None,
key: Optional[Any] = None,
id: Optional[Any] = None,
class_name: Optional[Any] = None,
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
on_blur: Optional[EventType[[], BASE_STATE]] = None,
on_click: Optional[EventType[[], BASE_STATE]] = None,
on_context_menu: Optional[EventType[[], BASE_STATE]] = None,
on_double_click: Optional[EventType[[], BASE_STATE]] = None,
on_focus: Optional[EventType[[], BASE_STATE]] = None,
on_mount: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_down: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_move: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_out: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_over: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_up: Optional[EventType[[], BASE_STATE]] = None,
on_scroll: Optional[EventType[[], BASE_STATE]] = None,
on_unmount: Optional[EventType[[], BASE_STATE]] = None,
**props,
) -> "StickyBadge":
"""Create the sticky badge.
Returns:
The sticky badge.
"""
...
def add_style(self): ...
class StickyNamespace(ComponentNamespace):
label = staticmethod(StickyLabel.create)
logo = staticmethod(StickyLogo.create)
@staticmethod
def __call__(
*children,
download: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
href: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
href_lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
media: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
ping: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
referrer_policy: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
rel: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
shape: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
target: 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]
] = None,
content_editable: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
context_menu: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
dir: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
draggable: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
enter_key_hint: Optional[
Union[Var[Union[bool, int, str]], bool, int, str]
] = None,
hidden: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
input_mode: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
item_prop: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
role: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
slot: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
spell_check: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
tab_index: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
title: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
style: Optional[Style] = None,
key: Optional[Any] = None,
id: Optional[Any] = None,
class_name: Optional[Any] = None,
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
on_blur: Optional[EventType[[], BASE_STATE]] = None,
on_click: Optional[EventType[[], BASE_STATE]] = None,
on_context_menu: Optional[EventType[[], BASE_STATE]] = None,
on_double_click: Optional[EventType[[], BASE_STATE]] = None,
on_focus: Optional[EventType[[], BASE_STATE]] = None,
on_mount: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_down: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_move: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_out: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_over: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_up: Optional[EventType[[], BASE_STATE]] = None,
on_scroll: Optional[EventType[[], BASE_STATE]] = None,
on_unmount: Optional[EventType[[], BASE_STATE]] = None,
**props,
) -> "StickyBadge":
"""Create the sticky badge.
Returns:
The sticky badge.
"""
...
sticky = StickyNamespace()

View File

@ -347,7 +347,7 @@ class DataEditor(NoSSRComponent):
data_callback = f"getData_{editor_id}"
self.get_cell_content = Var(_js_expr=data_callback)
code = [f"function {data_callback}([col, row])" "{"]
code = [f"function {data_callback}([col, row]){{"]
columns_path = str(self.columns)
data_path = str(self.data)

View File

@ -5,11 +5,15 @@ from typing import Union
import reflex as rx
def svg_logo(color: Union[str, rx.Var[str]] = rx.color_mode_cond("#110F1F", "white")):
def svg_logo(
color: Union[str, rx.Var[str]] = rx.color_mode_cond("#110F1F", "white"),
**props,
):
"""A Reflex logo SVG.
Args:
color: The color of the logo.
props: Extra props to pass to the svg component.
Returns:
The Reflex logo SVG.
@ -29,11 +33,14 @@ def svg_logo(color: Union[str, rx.Var[str]] = rx.color_mode_cond("#110F1F", "whi
return rx.el.svg(
*[logo_path(d) for d in paths],
width="56",
height="12",
viewBox="0 0 56 12",
rx.el.title("Reflex"),
aria_label="Reflex",
role="img",
width=props.pop("width", "56"),
height=props.pop("height", "12"),
fill=color,
xmlns="http://www.w3.org/2000/svg",
**props,
)

View File

@ -1,6 +1,7 @@
"""Skeleton theme from Radix components."""
from reflex.components.core.breakpoints import Responsive
from reflex.constants.compiler import MemoizationMode
from reflex.vars.base import Var
from ..base import RadixLoadingProp, RadixThemesComponent
@ -29,5 +30,7 @@ class Skeleton(RadixLoadingProp, RadixThemesComponent):
# The maximum height of the skeleton
max_height: Var[Responsive[str]]
_memoization_mode = MemoizationMode(recursive=False)
skeleton = Skeleton.create

View File

@ -3,6 +3,7 @@
from typing import Dict, Literal, Union
from reflex.components.component import Component
from reflex.constants.compiler import MemoizationMode
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
from reflex.utils import format
from reflex.vars.base import Var
@ -94,6 +95,8 @@ class Tooltip(RadixThemesComponent):
# Fired when the pointer is down outside the tooltip.
on_pointer_down_outside: EventHandler[no_args_event_spec]
_memoization_mode = MemoizationMode(recursive=False)
@classmethod
def create(cls, *children, **props) -> Component:
"""Initialize the Tooltip component.

View File

@ -177,7 +177,7 @@ class ToastNamespace(ComponentNamespace):
@staticmethod
def __call__(
message: Union[str, Var] = "", level: Optional[str] = None, **props
) -> "Optional[EventSpec]":
) -> "EventSpec":
"""Send a toast message.
Args:

View File

@ -703,6 +703,9 @@ class Config(Base):
# Path to file containing key-values pairs to override in the environment; Dotenv format.
env_file: Optional[str] = None
# Whether to display the sticky "Built with Reflex" badge on all pages.
show_built_with_reflex: bool = True
# Whether the app is running in the reflex cloud environment.
is_reflex_cloud: bool = False

View File

@ -772,7 +772,7 @@ def _validate_project_info():
pyproject_toml = _get_package_config()
project = pyproject_toml["project"]
console.print(
f'Double check the information before publishing: {project["name"]} version {project["version"]}'
f"Double check the information before publishing: {project['name']} version {project['version']}"
)
console.print("Update or enter to keep the current information.")
@ -784,7 +784,7 @@ def _validate_project_info():
author["name"] = console.ask("Author Name", default=author.get("name", ""))
author["email"] = console.ask("Author Email", default=author.get("email", ""))
console.print(f'Current keywords are: {project.get("keywords") or []}')
console.print(f"Current keywords are: {project.get('keywords') or []}")
keyword_action = console.ask(
"Keep, replace or append?", choices=["k", "r", "a"], default="k"
)

View File

@ -332,7 +332,7 @@ class EventSpec(EventActionsMixin):
arg = None
try:
for arg in args:
values.append(LiteralVar.create(value=arg)) # noqa: PERF401
values.append(LiteralVar.create(value=arg)) # noqa: PERF401, RUF100
except TypeError as e:
raise EventHandlerTypeError(
f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."

View File

@ -13,16 +13,25 @@ from .client_state import ClientStateVar as ClientStateVar
from .layout import layout as layout
from .misc import run_in_thread as run_in_thread
warn(
"`rx._x` contains experimental features and might be removed at any time in the future .",
)
_EMITTED_PROMOTION_WARNINGS = set()
class ExperimentalNamespace(SimpleNamespace):
"""Namespace for experimental features."""
def __getattribute__(self, item: str):
"""Get attribute from the namespace.
Args:
item: attribute name.
Returns:
The attribute.
"""
warn(
"`rx._x` contains experimental features and might be removed at any time in the future.",
dedupe=True,
)
return super().__getattribute__(item)
@property
def toast(self):
"""Temporary property returning the toast namespace.
@ -55,9 +64,10 @@ class ExperimentalNamespace(SimpleNamespace):
Args:
component_name: name of the component.
"""
if component_name not in _EMITTED_PROMOTION_WARNINGS:
_EMITTED_PROMOTION_WARNINGS.add(component_name)
warn(f"`rx._x.{component_name}` was promoted to `rx.{component_name}`.")
warn(
f"`rx._x.{component_name}` was promoted to `rx.{component_name}`.",
dedupe=True,
)
_x = ExperimentalNamespace(

View File

@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Optional
from reflex import constants
from reflex.event import Event, get_hydrate_event
from reflex.middleware.middleware import Middleware
from reflex.state import BaseState, StateUpdate
from reflex.state import BaseState, StateUpdate, _resolve_delta
if TYPE_CHECKING:
from reflex.app import App
@ -42,7 +42,7 @@ class HydrateMiddleware(Middleware):
setattr(state, constants.CompileVars.IS_HYDRATED, False)
# Get the initial state.
delta = state.dict()
delta = await _resolve_delta(state.dict())
# since a full dict was captured, clean any dirtiness
state._clean()

View File

@ -26,6 +26,8 @@ except TypeError:
# Fallback for older typer versions.
cli = typer.Typer(add_completion=False)
SHOW_BUILT_WITH_REFLEX_INFO = "https://reflex.dev/docs/hosting/reflex-branding/"
# Get the config.
config = get_config()
@ -186,6 +188,15 @@ def _run(
prerequisites.check_latest_package_version(constants.Reflex.MODULE_NAME)
if frontend:
if not config.show_built_with_reflex:
# The sticky badge may be disabled at runtime for team/enterprise tiers.
prerequisites.check_config_option_in_tier(
option_name="show_built_with_reflex",
allowed_tiers=["team", "enterprise"],
fallback_value=True,
help_link=SHOW_BUILT_WITH_REFLEX_INFO,
)
# Get the app module.
prerequisites.get_compiled_app()
@ -324,6 +335,15 @@ def export(
if prerequisites.needs_reinit(frontend=True):
_init(name=config.app_name, loglevel=loglevel)
if frontend and not config.show_built_with_reflex:
# The sticky badge may be disabled on export for team/enterprise tiers.
prerequisites.check_config_option_in_tier(
option_name="show_built_with_reflex",
allowed_tiers=["team", "enterprise"],
fallback_value=False,
help_link=SHOW_BUILT_WITH_REFLEX_INFO,
)
export_utils.export(
zipping=zipping,
frontend=frontend,
@ -518,6 +538,15 @@ def deploy(
check_version()
if not config.show_built_with_reflex:
# The sticky badge may be disabled on deploy for pro/team/enterprise tiers.
prerequisites.check_config_option_in_tier(
option_name="show_built_with_reflex",
allowed_tiers=["pro", "team", "enterprise"],
fallback_value=True,
help_link=SHOW_BUILT_WITH_REFLEX_INFO,
)
# Set the log level.
console.set_log_level(loglevel)

View File

@ -15,7 +15,6 @@ import time
import typing
import uuid
from abc import ABC, abstractmethod
from collections import defaultdict
from hashlib import md5
from pathlib import Path
from types import FunctionType, MethodType
@ -329,6 +328,25 @@ def get_var_for_field(cls: Type[BaseState], f: ModelField):
)
async def _resolve_delta(delta: Delta) -> Delta:
"""Await all coroutines in the delta.
Args:
delta: The delta to process.
Returns:
The same delta dict with all coroutines resolved to their return value.
"""
tasks = {}
for state_name, state_delta in delta.items():
for var_name, value in state_delta.items():
if asyncio.iscoroutine(value):
tasks[state_name, var_name] = asyncio.create_task(value)
for (state_name, var_name), task in tasks.items():
delta[state_name][var_name] = await task
return delta
class BaseState(Base, ABC, extra=pydantic.Extra.allow):
"""The state of the app."""
@ -356,11 +374,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
# A set of subclassses of this class.
class_subclasses: ClassVar[Set[Type[BaseState]]] = set()
# Mapping of var name to set of computed variables that depend on it
_computed_var_dependencies: ClassVar[Dict[str, Set[str]]] = {}
# Mapping of var name to set of substates that depend on it
_substate_var_dependencies: ClassVar[Dict[str, Set[str]]] = {}
# Mapping of var name to set of (state_full_name, var_name) that depend on it.
_var_dependencies: ClassVar[Dict[str, Set[Tuple[str, str]]]] = {}
# Set of vars which always need to be recomputed
_always_dirty_computed_vars: ClassVar[Set[str]] = set()
@ -368,6 +383,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
# Set of substates which always need to be recomputed
_always_dirty_substates: ClassVar[Set[str]] = set()
# Set of states which might need to be recomputed if vars in this state change.
_potentially_dirty_states: ClassVar[Set[str]] = set()
# The parent state.
parent_state: Optional[BaseState] = None
@ -519,6 +537,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
# Reset dirty substate tracking for this class.
cls._always_dirty_substates = set()
cls._potentially_dirty_states = set()
# Get the parent vars.
parent_state = cls.get_parent_state()
@ -622,8 +641,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
setattr(cls, name, handler)
# Initialize per-class var dependency tracking.
cls._computed_var_dependencies = defaultdict(set)
cls._substate_var_dependencies = defaultdict(set)
cls._var_dependencies = {}
cls._init_var_dependency_dicts()
@staticmethod
@ -768,26 +786,27 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
Additional updates tracking dicts for vars and substates that always
need to be recomputed.
"""
inherited_vars = set(cls.inherited_vars).union(
set(cls.inherited_backend_vars),
)
for cvar_name, cvar in cls.computed_vars.items():
# Add the dependencies.
for var in cvar._deps(objclass=cls):
cls._computed_var_dependencies[var].add(cvar_name)
if var in inherited_vars:
# track that this substate depends on its parent for this var
state_name = cls.get_name()
parent_state = cls.get_parent_state()
while parent_state is not None and var in {
**parent_state.vars,
**parent_state.backend_vars,
if not cvar._cache:
# Do not perform dep calculation when cache=False (these are always dirty).
continue
for state_name, dvar_set in cvar._deps(objclass=cls).items():
state_cls = cls.get_root_state().get_class_substate(state_name)
for dvar in dvar_set:
defining_state_cls = state_cls
while dvar in {
*defining_state_cls.inherited_vars,
*defining_state_cls.inherited_backend_vars,
}:
parent_state._substate_var_dependencies[var].add(state_name)
state_name, parent_state = (
parent_state.get_name(),
parent_state.get_parent_state(),
)
parent_state = defining_state_cls.get_parent_state()
if parent_state is not None:
defining_state_cls = parent_state
defining_state_cls._var_dependencies.setdefault(dvar, set()).add(
(cls.get_full_name(), cvar_name)
)
defining_state_cls._potentially_dirty_states.add(
cls.get_full_name()
)
# ComputedVar with cache=False always need to be recomputed
cls._always_dirty_computed_vars = {
@ -902,6 +921,17 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
raise ValueError(f"Only one parent state is allowed {parent_states}.")
return parent_states[0] if len(parent_states) == 1 else None
@classmethod
@functools.lru_cache()
def get_root_state(cls) -> Type[BaseState]:
"""Get the root state.
Returns:
The root state.
"""
parent_state = cls.get_parent_state()
return cls if parent_state is None else parent_state.get_root_state()
@classmethod
def get_substates(cls) -> set[Type[BaseState]]:
"""Get the substates of the state.
@ -1351,7 +1381,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
super().__setattr__(name, value)
# Add the var to the dirty list.
if name in self.vars or name in self._computed_var_dependencies:
if name in self.base_vars:
self.dirty_vars.add(name)
self._mark_dirty()
@ -1422,64 +1452,21 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
return self.substates[path[0]].get_substate(path[1:])
@classmethod
def _get_common_ancestor(cls, other: Type[BaseState]) -> str:
"""Find the name of the nearest common ancestor shared by this and the other state.
Args:
other: The other state.
def _get_potentially_dirty_states(cls) -> set[type[BaseState]]:
"""Get substates which may have dirty vars due to dependencies.
Returns:
Full name of the nearest common ancestor.
The set of potentially dirty substate classes.
"""
common_ancestor_parts = []
for part1, part2 in zip(
cls.get_full_name().split("."),
other.get_full_name().split("."),
strict=True,
):
if part1 != part2:
break
common_ancestor_parts.append(part1)
return ".".join(common_ancestor_parts)
@classmethod
def _determine_missing_parent_states(
cls, target_state_cls: Type[BaseState]
) -> tuple[str, list[str]]:
"""Determine the missing parent states between the target_state_cls and common ancestor of this state.
Args:
target_state_cls: The class of the state to find missing parent states for.
Returns:
The name of the common ancestor and the list of missing parent states.
"""
common_ancestor_name = cls._get_common_ancestor(target_state_cls)
common_ancestor_parts = common_ancestor_name.split(".")
target_state_parts = tuple(target_state_cls.get_full_name().split("."))
relative_target_state_parts = target_state_parts[len(common_ancestor_parts) :]
# Determine which parent states to fetch from the common ancestor down to the target_state_cls.
fetch_parent_states = [common_ancestor_name]
for relative_parent_state_name in relative_target_state_parts:
fetch_parent_states.append(
".".join((fetch_parent_states[-1], relative_parent_state_name))
)
return common_ancestor_name, fetch_parent_states[1:-1]
def _get_parent_states(self) -> list[tuple[str, BaseState]]:
"""Get all parent state instances up to the root of the state tree.
Returns:
A list of tuples containing the name and the instance of each parent state.
"""
parent_states_with_name = []
parent_state = self
while parent_state.parent_state is not None:
parent_state = parent_state.parent_state
parent_states_with_name.append((parent_state.get_full_name(), parent_state))
return parent_states_with_name
return {
cls.get_class_substate(substate_name)
for substate_name in cls._always_dirty_substates
}.union(
{
cls.get_root_state().get_class_substate(substate_name)
for substate_name in cls._potentially_dirty_states
}
)
def _get_root_state(self) -> BaseState:
"""Get the root state of the state tree.
@ -1492,55 +1479,38 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
parent_state = parent_state.parent_state
return parent_state
async def _populate_parent_states(self, target_state_cls: Type[BaseState]):
"""Populate substates in the tree between the target_state_cls and common ancestor of this state.
async def _get_state_from_redis(self, state_cls: Type[T_STATE]) -> T_STATE:
"""Get a state instance from redis.
Args:
target_state_cls: The class of the state to populate parent states for.
state_cls: The class of the state.
Returns:
The parent state instance of target_state_cls.
The instance of state_cls associated with this state's client_token.
Raises:
RuntimeError: If redis is not used in this backend process.
StateMismatchError: If the state instance is not of the expected type.
"""
# Then get the target state and all its substates.
state_manager = get_state_manager()
if not isinstance(state_manager, StateManagerRedis):
raise RuntimeError(
f"Cannot populate parent states of {target_state_cls.get_full_name()} without redis. "
f"Requested state {state_cls.get_full_name()} is not cached and cannot be accessed without redis. "
"(All states should already be available -- this is likely a bug).",
)
state_in_redis = await state_manager.get_state(
token=_substate_key(self.router.session.client_token, state_cls),
top_level=False,
for_state_instance=self,
)
# Find the missing parent states up to the common ancestor.
(
common_ancestor_name,
missing_parent_states,
) = self._determine_missing_parent_states(target_state_cls)
# Fetch all missing parent states and link them up to the common ancestor.
parent_states_tuple = self._get_parent_states()
root_state = parent_states_tuple[-1][1]
parent_states_by_name = dict(parent_states_tuple)
parent_state = parent_states_by_name[common_ancestor_name]
for parent_state_name in missing_parent_states:
try:
parent_state = root_state.get_substate(parent_state_name.split("."))
# The requested state is already cached, do NOT fetch it again.
continue
except ValueError:
# The requested state is missing, fetch from redis.
pass
parent_state = await state_manager.get_state(
token=_substate_key(
self.router.session.client_token, parent_state_name
),
top_level=False,
get_substates=False,
parent_state=parent_state,
if not isinstance(state_in_redis, state_cls):
raise StateMismatchError(
f"Searched for state {state_cls.get_full_name()} but found {state_in_redis}."
)
# Return the direct parent of target_state_cls for subsequent linking.
return parent_state
return state_in_redis
def _get_state_from_cache(self, state_cls: Type[T_STATE]) -> T_STATE:
"""Get a state instance from the cache.
@ -1562,44 +1532,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
)
return substate
async def _get_state_from_redis(self, state_cls: Type[T_STATE]) -> T_STATE:
"""Get a state instance from redis.
Args:
state_cls: The class of the state.
Returns:
The instance of state_cls associated with this state's client_token.
Raises:
RuntimeError: If redis is not used in this backend process.
StateMismatchError: If the state instance is not of the expected type.
"""
# Fetch all missing parent states from redis.
parent_state_of_state_cls = await self._populate_parent_states(state_cls)
# Then get the target state and all its substates.
state_manager = get_state_manager()
if not isinstance(state_manager, StateManagerRedis):
raise RuntimeError(
f"Requested state {state_cls.get_full_name()} is not cached and cannot be accessed without redis. "
"(All states should already be available -- this is likely a bug).",
)
state_in_redis = await state_manager.get_state(
token=_substate_key(self.router.session.client_token, state_cls),
top_level=False,
get_substates=True,
parent_state=parent_state_of_state_cls,
)
if not isinstance(state_in_redis, state_cls):
raise StateMismatchError(
f"Searched for state {state_cls.get_full_name()} but found {state_in_redis}."
)
return state_in_redis
async def get_state(self, state_cls: Type[T_STATE]) -> T_STATE:
"""Get an instance of the state associated with this token.
@ -1738,7 +1670,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
f"Your handler {handler.fn.__qualname__} must only return/yield: None, Events or other EventHandlers referenced by their class (not using `self`)"
)
def _as_state_update(
async def _as_state_update(
self,
handler: EventHandler,
events: EventSpec | list[EventSpec] | None,
@ -1766,7 +1698,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
try:
# Get the delta after processing the event.
delta = state.get_delta()
delta = await _resolve_delta(state.get_delta())
state._clean()
return StateUpdate(
@ -1866,24 +1798,28 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
# Handle async generators.
if inspect.isasyncgen(events):
async for event in events:
yield state._as_state_update(handler, event, final=False)
yield state._as_state_update(handler, events=None, final=True)
yield await state._as_state_update(handler, event, final=False)
yield await state._as_state_update(handler, events=None, final=True)
# Handle regular generators.
elif inspect.isgenerator(events):
try:
while True:
yield state._as_state_update(handler, next(events), final=False)
yield await state._as_state_update(
handler, next(events), final=False
)
except StopIteration as si:
# the "return" value of the generator is not available
# in the loop, we must catch StopIteration to access it
if si.value is not None:
yield state._as_state_update(handler, si.value, final=False)
yield state._as_state_update(handler, events=None, final=True)
yield await state._as_state_update(
handler, si.value, final=False
)
yield await state._as_state_update(handler, events=None, final=True)
# Handle regular event chains.
else:
yield state._as_state_update(handler, events, final=True)
yield await state._as_state_update(handler, events, final=True)
# If an error occurs, throw a window alert.
except Exception as ex:
@ -1893,7 +1829,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
prerequisites.get_and_validate_app().app.backend_exception_handler(ex)
)
yield state._as_state_update(
yield await state._as_state_update(
handler,
event_specs,
final=True,
@ -1901,15 +1837,28 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
def _mark_dirty_computed_vars(self) -> None:
"""Mark ComputedVars that need to be recalculated based on dirty_vars."""
# Append expired computed vars to dirty_vars to trigger recalculation
self.dirty_vars.update(self._expired_computed_vars())
# Append always dirty computed vars to dirty_vars to trigger recalculation
self.dirty_vars.update(self._always_dirty_computed_vars)
dirty_vars = self.dirty_vars
while dirty_vars:
calc_vars, dirty_vars = dirty_vars, set()
for cvar in self._dirty_computed_vars(from_vars=calc_vars):
self.dirty_vars.add(cvar)
for state_name, cvar in self._dirty_computed_vars(from_vars=calc_vars):
if state_name == self.get_full_name():
defining_state = self
else:
defining_state = self._get_root_state().get_substate(
tuple(state_name.split("."))
)
defining_state.dirty_vars.add(cvar)
dirty_vars.add(cvar)
actual_var = self.computed_vars.get(cvar)
actual_var = defining_state.computed_vars.get(cvar)
if actual_var is not None:
actual_var.mark_dirty(instance=self)
actual_var.mark_dirty(instance=defining_state)
if defining_state is not self:
defining_state._mark_dirty()
def _expired_computed_vars(self) -> set[str]:
"""Determine ComputedVars that need to be recalculated based on the expiration time.
@ -1925,7 +1874,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
def _dirty_computed_vars(
self, from_vars: set[str] | None = None, include_backend: bool = True
) -> set[str]:
) -> set[tuple[str, str]]:
"""Determine ComputedVars that need to be recalculated based on the given vars.
Args:
@ -1936,33 +1885,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
Set of computed vars to include in the delta.
"""
return {
cvar
(state_name, cvar)
for dirty_var in from_vars or self.dirty_vars
for cvar in self._computed_var_dependencies[dirty_var]
for state_name, cvar in self._var_dependencies.get(dirty_var, set())
if include_backend or not self.computed_vars[cvar]._backend
}
@classmethod
def _potentially_dirty_substates(cls) -> set[Type[BaseState]]:
"""Determine substates which could be affected by dirty vars in this state.
Returns:
Set of State classes that may need to be fetched to recalc computed vars.
"""
# _always_dirty_substates need to be fetched to recalc computed vars.
fetch_substates = {
cls.get_class_substate((cls.get_name(), *substate_name.split(".")))
for substate_name in cls._always_dirty_substates
}
for dependent_substates in cls._substate_var_dependencies.values():
fetch_substates.update(
{
cls.get_class_substate((cls.get_name(), *substate_name.split(".")))
for substate_name in dependent_substates
}
)
return fetch_substates
def get_delta(self) -> Delta:
"""Get the delta for the state.
@ -1971,21 +1899,15 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
"""
delta = {}
# Apply dirty variables down into substates
self.dirty_vars.update(self._always_dirty_computed_vars)
self._mark_dirty()
self._mark_dirty_computed_vars()
frontend_computed_vars: set[str] = {
name for name, cv in self.computed_vars.items() if not cv._backend
}
# Return the dirty vars for this instance, any cached/dependent computed vars,
# and always dirty computed vars (cache=False)
delta_vars = (
self.dirty_vars.intersection(self.base_vars)
.union(self.dirty_vars.intersection(frontend_computed_vars))
.union(self._dirty_computed_vars(include_backend=False))
.union(self._always_dirty_computed_vars)
delta_vars = self.dirty_vars.intersection(self.base_vars).union(
self.dirty_vars.intersection(frontend_computed_vars)
)
subdelta: Dict[str, Any] = {
@ -2015,23 +1937,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
self.parent_state.dirty_substates.add(self.get_name())
self.parent_state._mark_dirty()
# Append expired computed vars to dirty_vars to trigger recalculation
self.dirty_vars.update(self._expired_computed_vars())
# have to mark computed vars dirty to allow access to newly computed
# values within the same ComputedVar function
self._mark_dirty_computed_vars()
self._mark_dirty_substates()
def _mark_dirty_substates(self):
"""Propagate dirty var / computed var status into substates."""
substates = self.substates
for var in self.dirty_vars:
for substate_name in self._substate_var_dependencies[var]:
self.dirty_substates.add(substate_name)
substate = substates[substate_name]
substate.dirty_vars.add(var)
substate._mark_dirty()
def _update_was_touched(self):
"""Update the _was_touched flag based on dirty_vars."""
@ -2103,11 +2011,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
The object as a dictionary.
"""
if include_computed:
# Apply dirty variables down into substates to allow never-cached ComputedVar to
# trigger recalculation of dependent vars
self.dirty_vars.update(self._always_dirty_computed_vars)
self._mark_dirty()
self._mark_dirty_computed_vars()
base_vars = {
prop_name: self.get_value(prop_name) for prop_name in self.base_vars
}
@ -2824,7 +2728,7 @@ class StateProxy(wrapt.ObjectProxy):
await self.__wrapped__.get_state(state_cls), parent_state_proxy=self
)
def _as_state_update(self, *args, **kwargs) -> StateUpdate:
async def _as_state_update(self, *args, **kwargs) -> StateUpdate:
"""Temporarily allow mutability to access parent_state.
Args:
@ -2837,7 +2741,7 @@ class StateProxy(wrapt.ObjectProxy):
original_mutable = self._self_mutable
self._self_mutable = True
try:
return self.__wrapped__._as_state_update(*args, **kwargs)
return await self.__wrapped__._as_state_update(*args, **kwargs)
finally:
self._self_mutable = original_mutable
@ -3313,103 +3217,106 @@ class StateManagerRedis(StateManager):
b"evicted",
}
async def _get_parent_state(
self, token: str, state: BaseState | None = None
) -> BaseState | None:
"""Get the parent state for the state requested in the token.
def _get_required_state_classes(
self,
target_state_cls: Type[BaseState],
subclasses: bool = False,
required_state_classes: set[Type[BaseState]] | None = None,
) -> set[Type[BaseState]]:
"""Recursively determine which states are required to fetch the target state.
This will always include potentially dirty substates that depend on vars
in the target_state_cls.
Args:
token: The token to get the state for (_substate_key).
state: The state instance to get parent state for.
target_state_cls: The target state class being fetched.
subclasses: Whether to include subclasses of the target state.
required_state_classes: Recursive argument tracking state classes that have already been seen.
Returns:
The parent state for the state requested by the token or None if there is no such parent.
The set of state classes required to fetch the target state.
"""
parent_state = None
client_token, state_path = _split_substate_key(token)
parent_state_name = state_path.rpartition(".")[0]
if parent_state_name:
cached_substates = None
if state is not None:
cached_substates = [state]
# Retrieve the parent state to populate event handlers onto this substate.
parent_state = await self.get_state(
token=_substate_key(client_token, parent_state_name),
top_level=False,
get_substates=False,
cached_substates=cached_substates,
if required_state_classes is None:
required_state_classes = set()
# Get the substates if requested.
if subclasses:
for substate in target_state_cls.get_substates():
self._get_required_state_classes(
substate,
subclasses=True,
required_state_classes=required_state_classes,
)
if target_state_cls in required_state_classes:
return required_state_classes
required_state_classes.add(target_state_cls)
# Get dependent substates.
for pd_substates in target_state_cls._get_potentially_dirty_states():
self._get_required_state_classes(
pd_substates,
subclasses=False,
required_state_classes=required_state_classes,
)
return parent_state
async def _populate_substates(
# Get the parent state if it exists.
if parent_state := target_state_cls.get_parent_state():
self._get_required_state_classes(
parent_state,
subclasses=False,
required_state_classes=required_state_classes,
)
return required_state_classes
def _get_populated_states(
self,
token: str,
state: BaseState,
all_substates: bool = False,
):
"""Fetch and link substates for the given state instance.
There is no return value; the side-effect is that `state` will have `substates` populated,
and each substate will have its `parent_state` set to `state`.
target_state: BaseState,
populated_states: dict[str, BaseState] | None = None,
) -> dict[str, BaseState]:
"""Recursively determine which states from target_state are already fetched.
Args:
token: The token to get the state for.
state: The state instance to populate substates for.
all_substates: Whether to fetch all substates or just required substates.
target_state: The state to check for populated states.
populated_states: Recursive argument tracking states seen in previous calls.
Returns:
A dictionary of state full name to state instance.
"""
client_token, _ = _split_substate_key(token)
if all_substates:
# All substates are requested.
fetch_substates = state.get_substates()
else:
# Only _potentially_dirty_substates need to be fetched to recalc computed vars.
fetch_substates = state._potentially_dirty_substates()
tasks = {}
# Retrieve the necessary substates from redis.
for substate_cls in fetch_substates:
if substate_cls.get_name() in state.substates:
continue
substate_name = substate_cls.get_name()
tasks[substate_name] = asyncio.create_task(
self.get_state(
token=_substate_key(client_token, substate_cls),
top_level=False,
get_substates=all_substates,
parent_state=state,
)
if populated_states is None:
populated_states = {}
if target_state.get_full_name() in populated_states:
return populated_states
populated_states[target_state.get_full_name()] = target_state
for substate in target_state.substates.values():
self._get_populated_states(substate, populated_states=populated_states)
if target_state.parent_state is not None:
self._get_populated_states(
target_state.parent_state, populated_states=populated_states
)
for substate_name, substate_task in tasks.items():
state.substates[substate_name] = await substate_task
return populated_states
@override
async def get_state(
self,
token: str,
top_level: bool = True,
get_substates: bool = True,
parent_state: BaseState | None = None,
cached_substates: list[BaseState] | None = None,
for_state_instance: BaseState | None = None,
) -> BaseState:
"""Get the state for a token.
Args:
token: The token to get the state for.
top_level: If true, return an instance of the top-level state (self.state).
get_substates: If true, also retrieve substates.
parent_state: If provided, use this parent_state instead of getting it from redis.
cached_substates: If provided, attach these substates to the state.
for_state_instance: If provided, attach the requested states to this existing state tree.
Returns:
The state for the token.
Raises:
RuntimeError: when the state_cls is not specified in the token
RuntimeError: when the state_cls is not specified in the token, or when the parent state for a
requested state was not fetched.
"""
# Split the actual token from the fully qualified substate name.
_, state_path = _split_substate_key(token)
token, state_path = _split_substate_key(token)
if state_path:
# Get the State class associated with the given path.
state_cls = self.state.get_class_substate(state_path)
@ -3418,43 +3325,59 @@ class StateManagerRedis(StateManager):
f"StateManagerRedis requires token to be specified in the form of {{token}}_{{state_full_name}}, but got {token}"
)
# The deserialized or newly created (sub)state instance.
state = None
# Determine which states we already have.
flat_state_tree: dict[str, BaseState] = (
self._get_populated_states(for_state_instance) if for_state_instance else {}
)
# Fetch the serialized substate from redis.
redis_state = await self.redis.get(token)
# Determine which states from the tree need to be fetched.
required_state_classes = sorted(
self._get_required_state_classes(state_cls, subclasses=True)
- {type(s) for s in flat_state_tree.values()},
key=lambda x: x.get_full_name(),
)
if redis_state is not None:
# Deserialize the substate.
with contextlib.suppress(StateSchemaMismatchError):
state = BaseState._deserialize(data=redis_state)
if state is None:
# Key didn't exist or schema mismatch so create a new instance for this token.
state = state_cls(
init_substates=False,
_reflex_internal_init=True,
)
# Populate parent state if missing and requested.
if parent_state is None:
parent_state = await self._get_parent_state(token, state)
# Set up Bidirectional linkage between this state and its parent.
if parent_state is not None:
parent_state.substates[state.get_name()] = state
state.parent_state = parent_state
# Avoid fetching substates multiple times.
if cached_substates:
for substate in cached_substates:
state.substates[substate.get_name()] = substate
if substate.parent_state is None:
substate.parent_state = state
# Populate substates if requested.
await self._populate_substates(token, state, all_substates=get_substates)
redis_pipeline = self.redis.pipeline()
for state_cls in required_state_classes:
redis_pipeline.get(_substate_key(token, state_cls))
for state_cls, redis_state in zip(
required_state_classes,
await redis_pipeline.execute(),
strict=False,
):
state = None
if redis_state is not None:
# Deserialize the substate.
with contextlib.suppress(StateSchemaMismatchError):
state = BaseState._deserialize(data=redis_state)
if state is None:
# Key didn't exist or schema mismatch so create a new instance for this token.
state = state_cls(
init_substates=False,
_reflex_internal_init=True,
)
flat_state_tree[state.get_full_name()] = state
if state.get_parent_state() is not None:
parent_state_name, _dot, state_name = state.get_full_name().rpartition(
"."
)
parent_state = flat_state_tree.get(parent_state_name)
if parent_state is None:
raise RuntimeError(
f"Parent state for {state.get_full_name()} was not found "
"in the state tree, but should have already been fetched. "
"This is a bug",
)
parent_state.substates[state_name] = state
state.parent_state = parent_state
# To retain compatibility with previous implementation, by default, we return
# the top-level state by chasing `parent_state` pointers up the tree.
# the top-level state which should always be fetched or already cached.
if top_level:
return state._get_root_state()
return state
return flat_state_tree[self.state.get_full_name()]
return flat_state_tree[state_cls.get_full_name()]
@override
async def set_state(
@ -4154,12 +4077,19 @@ def reload_state_module(
state: Recursive argument for the state class to reload.
"""
# Clean out all potentially dirty states of reloaded modules.
for pd_state in tuple(state._potentially_dirty_states):
with contextlib.suppress(ValueError):
if (
state.get_root_state().get_class_substate(pd_state).__module__ == module
and module is not None
):
state._potentially_dirty_states.remove(pd_state)
for subclass in tuple(state.class_subclasses):
reload_state_module(module=module, state=subclass)
if subclass.__module__ == module and module is not None:
state.class_subclasses.remove(subclass)
state._always_dirty_substates.discard(subclass.get_name())
state._computed_var_dependencies = defaultdict(set)
state._substate_var_dependencies = defaultdict(set)
state._var_dependencies = {}
state._init_var_dependency_dicts()
state.get_class_substate.cache_clear()

View File

@ -488,7 +488,7 @@ def output_system_info():
dependencies.append(fnm_info)
if system == "Linux":
import distro
import distro # pyright: ignore[reportMissingImports]
os_version = distro.name(pretty=True)
else:

View File

@ -23,7 +23,7 @@ import zipfile
from datetime import datetime
from pathlib import Path
from types import ModuleType
from typing import Callable, List, NamedTuple, Optional
from typing import Any, Callable, List, NamedTuple, Optional
import httpx
import typer
@ -64,7 +64,7 @@ class Template:
name: str
description: str
code_url: str
demo_url: str
demo_url: str | None = None
@dataclasses.dataclass(frozen=True)
@ -912,7 +912,6 @@ def _update_next_config(
next_config = {
"basePath": config.frontend_path or "",
"compress": config.next_compression,
"reactStrictMode": config.react_strict_mode,
"trailingSlash": True,
"staticPageGenerationTimeout": config.static_page_generation_timeout,
}
@ -1294,7 +1293,7 @@ def validate_bun():
"""
bun_path = path_ops.get_bun_path()
if bun_path and bun_path.samefile(constants.Bun.DEFAULT_PATH):
if bun_path and not bun_path.samefile(constants.Bun.DEFAULT_PATH):
console.info(f"Using custom Bun path: {bun_path}")
bun_version = get_bun_version()
if not bun_version:
@ -1462,7 +1461,7 @@ def prompt_for_template_options(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:
def format_demo_url_str(url: str | None) -> str:
return f" ({url})" if url else ""
# Prompt the user to select a template.
@ -1855,7 +1854,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
[
resp.text,
"",
"" "def index() -> rx.Component:",
"def index() -> rx.Component:",
f" return {render_func_name}()",
"",
"",
@ -1979,3 +1978,40 @@ def is_generation_hash(template: str) -> bool:
True if the template is composed of 32 or more hex characters.
"""
return re.match(r"^[0-9a-f]{32,}$", template) is not None
def check_config_option_in_tier(
option_name: str,
allowed_tiers: list[str],
fallback_value: Any,
help_link: str | None = None,
):
"""Check if a config option is allowed for the authenticated user's current tier.
Args:
option_name: The name of the option to check.
allowed_tiers: The tiers that are allowed to use the option.
fallback_value: The fallback value if the option is not allowed.
help_link: The help link to show to a user that is authenticated.
"""
from reflex_cli.v2.utils import hosting
config = get_config()
authenticated_token = hosting.authenticated_token()
if not authenticated_token[0]:
the_remedy = (
"You are currently logged out. Run `reflex login` to access this option."
)
current_tier = "anonymous"
else:
current_tier = authenticated_token[1].get("tier", "").lower()
the_remedy = (
f"Your current subscription tier is `{current_tier}`. "
f"Please upgrade to {allowed_tiers} to access this option. "
)
if help_link:
the_remedy += f"See {help_link} for more information."
if current_tier not in allowed_tiers:
console.warn(f"Config option `{option_name}` is restricted. {the_remedy}")
setattr(config, option_name, fallback_value)
config._set_persistent(**{option_name: fallback_value})

View File

@ -622,7 +622,7 @@ def _generate_component_create_functiondef(
defaults=[],
)
definition = ast.FunctionDef(
definition = ast.FunctionDef( # pyright: ignore [reportCallIssue]
name="create",
args=create_args,
body=[ # pyright: ignore [reportArgumentType]
@ -684,7 +684,7 @@ def _generate_staticmethod_call_functiondef(
else []
),
)
definition = ast.FunctionDef(
definition = ast.FunctionDef( # pyright: ignore [reportCallIssue]
name="__call__",
args=call_args,
body=[
@ -699,6 +699,7 @@ def _generate_staticmethod_call_functiondef(
value=_get_type_hint(
typing.get_type_hints(clz.__call__).get("return", None),
type_hint_globals,
is_optional=False,
)
),
)

View File

@ -5,13 +5,13 @@ from __future__ import annotations
import contextlib
import dataclasses
import datetime
import dis
import functools
import inspect
import json
import random
import re
import string
import uuid
import warnings
from types import CodeType, FunctionType
from typing import (
@ -19,6 +19,7 @@ from typing import (
Any,
Callable,
ClassVar,
Coroutine,
Dict,
FrozenSet,
Generic,
@ -39,6 +40,7 @@ from typing import (
overload,
)
from sqlalchemy.orm import DeclarativeBase
from typing_extensions import ParamSpec, TypeGuard, deprecated, get_type_hints, override
from reflex import constants
@ -51,7 +53,6 @@ from reflex.utils.exceptions import (
VarAttributeError,
VarDependencyError,
VarTypeError,
VarValueError,
)
from reflex.utils.format import format_state_name
from reflex.utils.imports import (
@ -574,7 +575,7 @@ class Var(Generic[VAR_TYPE]):
@overload
@classmethod
def create( # type: ignore[override]
def create( # pyright: ignore[reportOverlappingOverload]
cls,
value: bool,
_var_data: VarData | None = None,
@ -582,7 +583,7 @@ class Var(Generic[VAR_TYPE]):
@overload
@classmethod
def create( # type: ignore[override]
def create(
cls,
value: int,
_var_data: VarData | None = None,
@ -606,7 +607,7 @@ class Var(Generic[VAR_TYPE]):
@overload
@classmethod
def create(
def create( # pyright: ignore[reportOverlappingOverload]
cls,
value: None,
_var_data: VarData | None = None,
@ -1667,17 +1668,92 @@ def figure_out_type(value: Any) -> types.GenericType:
return type(value)
class cached_property_no_lock(functools.cached_property): # noqa: N801
"""A special version of functools.cached_property that does not use a lock."""
GLOBAL_CACHE = {}
class cached_property: # noqa: N801
"""A cached property that caches the result of the function."""
def __init__(self, func: Callable):
"""Initialize the cached_property_no_lock.
"""Initialize the cached_property.
Args:
func: The function to cache.
"""
super().__init__(func)
self.lock = contextlib.nullcontext()
self._func = func
self._attrname = None
def __set_name__(self, owner: Any, name: str):
"""Set the name of the cached property.
Args:
owner: The owner of the cached property.
name: The name of the cached property.
Raises:
TypeError: If the cached property is assigned to two different names.
"""
if self._attrname is None:
self._attrname = name
original_del = getattr(owner, "__del__", None)
def delete_property(this: Any):
"""Delete the cached property.
Args:
this: The object to delete the cached property from.
"""
cached_field_name = "_reflex_cache_" + name
try:
unique_id = object.__getattribute__(this, cached_field_name)
except AttributeError:
if original_del is not None:
original_del(this)
return
if unique_id in GLOBAL_CACHE:
del GLOBAL_CACHE[unique_id]
if original_del is not None:
original_del(this)
owner.__del__ = delete_property
elif name != self._attrname:
raise TypeError(
"Cannot assign the same cached_property to two different names "
f"({self._attrname!r} and {name!r})."
)
def __get__(self, instance: Any, owner: Type | None = None):
"""Get the cached property.
Args:
instance: The instance to get the cached property from.
owner: The owner of the cached property.
Returns:
The cached property.
Raises:
TypeError: If the class does not have __set_name__.
"""
if self._attrname is None:
raise TypeError(
"Cannot use cached_property on a class without __set_name__."
)
cached_field_name = "_reflex_cache_" + self._attrname
try:
unique_id = object.__getattribute__(instance, cached_field_name)
except AttributeError:
unique_id = uuid.uuid4().int
object.__setattr__(instance, cached_field_name, unique_id)
if unique_id not in GLOBAL_CACHE:
GLOBAL_CACHE[unique_id] = self._func(instance)
return GLOBAL_CACHE[unique_id]
cached_property_no_lock = cached_property
class CachedVarOperation:
@ -1908,7 +1984,7 @@ class ComputedVar(Var[RETURN_TYPE]):
_initial_value: RETURN_TYPE | types.Unset = dataclasses.field(default=types.Unset())
# Explicit var dependencies to track
_static_deps: set[str] = dataclasses.field(default_factory=set)
_static_deps: dict[str | None, set[str]] = dataclasses.field(default_factory=dict)
# Whether var dependencies should be auto-determined
_auto_deps: bool = dataclasses.field(default=True)
@ -1978,37 +2054,81 @@ class ComputedVar(Var[RETURN_TYPE]):
object.__setattr__(self, "_update_interval", interval)
if deps is None:
deps = []
else:
for dep in deps:
if isinstance(dep, Var):
continue
if isinstance(dep, str) and dep != "":
continue
raise TypeError(
"ComputedVar dependencies must be Var instances or var names (non-empty strings)."
)
object.__setattr__(
self,
"_static_deps",
{dep._js_expr if isinstance(dep, Var) else dep for dep in deps},
self._calculate_static_deps(deps),
)
object.__setattr__(self, "_auto_deps", auto_deps)
object.__setattr__(self, "_fget", fget)
def _calculate_static_deps(
self,
deps: Union[List[Union[str, Var]], dict[str | None, set[str]]] | None = None,
) -> dict[str | None, set[str]]:
"""Calculate the static dependencies of the computed var from user input or existing dependencies.
Args:
deps: The user input dependencies or existing dependencies.
Returns:
The static dependencies.
"""
if isinstance(deps, dict):
# Assume a dict is coming from _replace, so no special processing.
return deps
_static_deps = {}
if deps is not None:
for dep in deps:
_static_deps = self._add_static_dep(dep, _static_deps)
return _static_deps
def _add_static_dep(
self, dep: Union[str, Var], deps: dict[str | None, set[str]] | None = None
) -> dict[str | None, set[str]]:
"""Add a static dependency to the computed var or existing dependency set.
Args:
dep: The dependency to add.
deps: The existing dependency set.
Returns:
The updated dependency set.
Raises:
TypeError: If the computed var dependencies are not Var instances or var names.
"""
if deps is None:
deps = self._static_deps
if isinstance(dep, Var):
state_name = (
all_var_data.state
if (all_var_data := dep._get_all_var_data()) and all_var_data.state
else None
)
if all_var_data is not None:
var_name = all_var_data.field_name
else:
var_name = dep._js_expr
deps.setdefault(state_name, set()).add(var_name)
elif isinstance(dep, str) and dep != "":
deps.setdefault(None, set()).add(dep)
else:
raise TypeError(
"ComputedVar dependencies must be Var instances or var names (non-empty strings)."
)
return deps
@override
def _replace(
self,
_var_type: Any = None,
merge_var_data: VarData | None = None,
**kwargs: Any,
) -> Self:
"""Replace the attributes of the ComputedVar.
Args:
_var_type: ignored in ComputedVar.
merge_var_data: VarData to merge into the existing VarData.
**kwargs: Var fields to update.
@ -2018,6 +2138,8 @@ class ComputedVar(Var[RETURN_TYPE]):
Raises:
TypeError: If kwargs contains keys that are not allowed.
"""
if "deps" in kwargs:
kwargs["deps"] = self._calculate_static_deps(kwargs["deps"])
field_values = {
"fget": kwargs.pop("fget", self._fget),
"initial_value": kwargs.pop("initial_value", self._initial_value),
@ -2074,6 +2196,13 @@ class ComputedVar(Var[RETURN_TYPE]):
return True
return datetime.datetime.now() - last_updated > self._update_interval
@overload
def __get__(
self: ComputedVar[bool],
instance: None,
owner: Type,
) -> BooleanVar: ...
@overload
def __get__(
self: ComputedVar[int] | ComputedVar[float],
@ -2158,125 +2287,67 @@ class ComputedVar(Var[RETURN_TYPE]):
setattr(instance, self._last_updated_attr, datetime.datetime.now())
value = getattr(instance, self._cache_attr)
self._check_deprecated_return_type(instance, value)
return value
def _check_deprecated_return_type(self, instance: BaseState, value: Any) -> None:
if not _isinstance(value, self._var_type):
console.error(
f"Computed var '{type(instance).__name__}.{self._js_expr}' must return"
f" type '{self._var_type}', got '{type(value)}'."
)
return value
def _deps(
self,
objclass: Type,
objclass: Type[BaseState],
obj: FunctionType | CodeType | None = None,
self_name: Optional[str] = None,
) -> set[str]:
) -> dict[str, set[str]]:
"""Determine var dependencies of this ComputedVar.
Save references to attributes accessed on "self". Recursively called
when the function makes a method call on "self" or define comprehensions
or nested functions that may reference "self".
Save references to attributes accessed on "self" or other fetched states.
Recursively called when the function makes a method call on "self" or
define comprehensions or nested functions that may reference "self".
Args:
objclass: the class obj this ComputedVar is attached to.
obj: the object to disassemble (defaults to the fget function).
self_name: if specified, look for this name in LOAD_FAST and LOAD_DEREF instructions.
Returns:
A set of variable names accessed by the given obj.
Raises:
VarValueError: if the function references the get_state, parent_state, or substates attributes
(cannot track deps in a related state, only implicitly via parent state).
A dictionary mapping state names to the set of variable names
accessed by the given obj.
"""
from .dep_tracking import DependencyTracker
d = {}
if self._static_deps:
d.update(self._static_deps)
# None is a placeholder for the current state class.
if None in d:
d[objclass.get_full_name()] = d.pop(None)
if not self._auto_deps:
return self._static_deps
d = self._static_deps.copy()
return d
if obj is None:
fget = self._fget
if fget is not None:
obj = cast(FunctionType, fget)
else:
return set()
with contextlib.suppress(AttributeError):
# unbox functools.partial
obj = cast(FunctionType, obj.func) # pyright: ignore [reportAttributeAccessIssue]
with contextlib.suppress(AttributeError):
# unbox EventHandler
obj = cast(FunctionType, obj.fn) # pyright: ignore [reportAttributeAccessIssue]
return d
if self_name is None and isinstance(obj, FunctionType):
try:
# the first argument to the function is the name of "self" arg
self_name = obj.__code__.co_varnames[0]
except (AttributeError, IndexError):
self_name = None
if self_name is None:
# cannot reference attributes on self if method takes no args
return set()
invalid_names = ["get_state", "parent_state", "substates", "get_substate"]
self_is_top_of_stack = False
for instruction in dis.get_instructions(obj):
if (
instruction.opname in ("LOAD_FAST", "LOAD_DEREF")
and instruction.argval == self_name
):
# bytecode loaded the class instance to the top of stack, next load instruction
# is referencing an attribute on self
self_is_top_of_stack = True
continue
if self_is_top_of_stack and instruction.opname in (
"LOAD_ATTR",
"LOAD_METHOD",
):
try:
ref_obj = getattr(objclass, instruction.argval)
except Exception:
ref_obj = None
if instruction.argval in invalid_names:
raise VarValueError(
f"Cached var {self!s} cannot access arbitrary state via `{instruction.argval}`."
)
if callable(ref_obj):
# recurse into callable attributes
d.update(
self._deps(
objclass=objclass,
obj=ref_obj, # pyright: ignore [reportArgumentType]
)
)
# recurse into property fget functions
elif isinstance(ref_obj, property) and not isinstance(
ref_obj, ComputedVar
):
d.update(
self._deps(
objclass=objclass,
obj=ref_obj.fget, # pyright: ignore [reportArgumentType]
)
)
elif (
instruction.argval in objclass.backend_vars
or instruction.argval in objclass.vars
):
# var access
d.add(instruction.argval)
elif instruction.opname == "LOAD_CONST" and isinstance(
instruction.argval, CodeType
):
# recurse into nested functions / comprehensions, which can reference
# instance attributes from the outer scope
d.update(
self._deps(
objclass=objclass,
obj=instruction.argval,
self_name=self_name,
)
)
self_is_top_of_stack = False
return d
try:
return DependencyTracker(
func=obj, state_cls=objclass, dependencies=d
).dependencies
except Exception as e:
console.warn(
"Failed to automatically determine dependencies for computed var "
f"{objclass.__name__}.{self._js_expr}: {e}. "
"Provide static_deps and set auto_deps=False to suppress this warning."
)
return d
def mark_dirty(self, instance: BaseState) -> None:
"""Mark this ComputedVar as dirty.
@ -2287,6 +2358,37 @@ class ComputedVar(Var[RETURN_TYPE]):
with contextlib.suppress(AttributeError):
delattr(instance, self._cache_attr)
def add_dependency(self, objclass: Type[BaseState], dep: Var):
"""Explicitly add a dependency to the ComputedVar.
After adding the dependency, when the `dep` changes, this computed var
will be marked dirty.
Args:
objclass: The class obj this ComputedVar is attached to.
dep: The dependency to add.
Raises:
VarDependencyError: If the dependency is not a Var instance with a
state and field name
"""
if all_var_data := dep._get_all_var_data():
state_name = all_var_data.state
if state_name:
var_name = all_var_data.field_name
if var_name:
self._static_deps.setdefault(state_name, set()).add(var_name)
objclass.get_root_state().get_class_substate(
state_name
)._var_dependencies.setdefault(var_name, set()).add(
(objclass.get_full_name(), self._js_expr)
)
return
raise VarDependencyError(
"ComputedVar dependencies must be Var instances with a state and "
f"field name, got {dep!r}."
)
def _determine_var_type(self) -> Type:
"""Get the type of the var.
@ -2323,6 +2425,126 @@ class DynamicRouteVar(ComputedVar[Union[str, List[str]]]):
pass
async def _default_async_computed_var(_self: BaseState) -> Any:
return None
@dataclasses.dataclass(
eq=False,
frozen=True,
init=False,
slots=True,
)
class AsyncComputedVar(ComputedVar[RETURN_TYPE]):
"""A computed var that wraps a coroutinefunction."""
_fget: Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]] = (
dataclasses.field(default=_default_async_computed_var)
)
@overload
def __get__(
self: AsyncComputedVar[bool],
instance: None,
owner: Type,
) -> BooleanVar: ...
@overload
def __get__(
self: AsyncComputedVar[int] | ComputedVar[float],
instance: None,
owner: Type,
) -> NumberVar: ...
@overload
def __get__(
self: AsyncComputedVar[str],
instance: None,
owner: Type,
) -> StringVar: ...
@overload
def __get__(
self: AsyncComputedVar[Mapping[DICT_KEY, DICT_VAL]],
instance: None,
owner: Type,
) -> ObjectVar[Mapping[DICT_KEY, DICT_VAL]]: ...
@overload
def __get__(
self: AsyncComputedVar[list[LIST_INSIDE]],
instance: None,
owner: Type,
) -> ArrayVar[list[LIST_INSIDE]]: ...
@overload
def __get__(
self: AsyncComputedVar[tuple[LIST_INSIDE, ...]],
instance: None,
owner: Type,
) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ...
@overload
def __get__(self, instance: None, owner: Type) -> AsyncComputedVar[RETURN_TYPE]: ...
@overload
def __get__(
self, instance: BaseState, owner: Type
) -> Coroutine[None, None, RETURN_TYPE]: ...
def __get__(
self, instance: BaseState | None, owner
) -> Var | Coroutine[None, None, RETURN_TYPE]:
"""Get the ComputedVar value.
If the value is already cached on the instance, return the cached value.
Args:
instance: the instance of the class accessing this computed var.
owner: the class that this descriptor is attached to.
Returns:
The value of the var for the given instance.
"""
if instance is None:
return super(AsyncComputedVar, self).__get__(instance, owner)
if not self._cache:
async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE:
value = await self.fget(instance)
self._check_deprecated_return_type(instance, value)
return value
return _awaitable_result()
else:
# handle caching
async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE:
if not hasattr(instance, self._cache_attr) or self.needs_update(
instance
):
# Set cache attr on state instance.
setattr(instance, self._cache_attr, await self.fget(instance))
# Ensure the computed var gets serialized to redis.
instance._was_touched = True
# Set the last updated timestamp on the state instance.
setattr(instance, self._last_updated_attr, datetime.datetime.now())
value = getattr(instance, self._cache_attr)
self._check_deprecated_return_type(instance, value)
return value
return _awaitable_result()
@property
def fget(self) -> Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]]:
"""Get the getter function.
Returns:
The getter function.
"""
return self._fget
if TYPE_CHECKING:
BASE_STATE = TypeVar("BASE_STATE", bound=BaseState)
@ -2381,6 +2603,7 @@ def computed_var(
Raises:
ValueError: If caching is disabled and an update interval is set.
VarDependencyError: If user supplies dependencies without caching.
ComputedVarSignatureError: If the getter function has more than one argument.
"""
if cache is False and interval is not None:
raise ValueError("Cannot set update interval without caching.")
@ -2393,10 +2616,27 @@ def computed_var(
if len(sign.parameters) != 1:
raise ComputedVarSignatureError(fget.__name__, signature=str(sign))
return ComputedVar(fget, cache=cache)
if inspect.iscoroutinefunction(fget):
computed_var_cls = AsyncComputedVar
else:
computed_var_cls = ComputedVar
return computed_var_cls(
fget,
initial_value=initial_value,
cache=cache,
deps=deps,
auto_deps=auto_deps,
interval=interval,
backend=backend,
**kwargs,
)
def wrapper(fget: Callable[[BASE_STATE], Any]) -> ComputedVar:
return ComputedVar(
if inspect.iscoroutinefunction(fget):
computed_var_cls = AsyncComputedVar
else:
computed_var_cls = ComputedVar
return computed_var_cls(
fget,
initial_value=initial_value,
cache=cache,
@ -2982,10 +3222,16 @@ def dispatch(
V = TypeVar("V")
BASE_TYPE = TypeVar("BASE_TYPE", bound=Base)
BASE_TYPE = TypeVar("BASE_TYPE", bound=Base | None)
SQLA_TYPE = TypeVar("SQLA_TYPE", bound=DeclarativeBase | None)
if TYPE_CHECKING:
from _typeshed import DataclassInstance
DATACLASS_TYPE = TypeVar("DATACLASS_TYPE", bound=DataclassInstance | None)
FIELD_TYPE = TypeVar("FIELD_TYPE")
MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping)
MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping | None)
class Field(Generic[FIELD_TYPE]):
@ -3030,6 +3276,18 @@ class Field(Generic[FIELD_TYPE]):
self: Field[BASE_TYPE], instance: None, owner: Any
) -> ObjectVar[BASE_TYPE]: ...
@overload
def __get__(
self: Field[SQLA_TYPE], instance: None, owner: Any
) -> ObjectVar[SQLA_TYPE]: ...
if TYPE_CHECKING:
@overload
def __get__(
self: Field[DATACLASS_TYPE], instance: None, owner: Any
) -> ObjectVar[DATACLASS_TYPE]: ...
@overload
def __get__(self, instance: None, owner: Any) -> Var[FIELD_TYPE]: ...

View File

@ -184,7 +184,7 @@ def date_compare_operation(
The result of the operation.
"""
return var_operation_return(
f"({lhs} { '<' if strict else '<='} {rhs})",
f"({lhs} {'<' if strict else '<='} {rhs})",
bool,
)

344
reflex/vars/dep_tracking.py Normal file
View File

@ -0,0 +1,344 @@
"""Collection of base classes."""
from __future__ import annotations
import contextlib
import dataclasses
import dis
import enum
import inspect
from types import CellType, CodeType, FunctionType
from typing import TYPE_CHECKING, Any, ClassVar, Type, cast
from reflex.utils.exceptions import VarValueError
if TYPE_CHECKING:
from reflex.state import BaseState
from .base import Var
CellEmpty = object()
def get_cell_value(cell: CellType) -> Any:
"""Get the value of a cell object.
Args:
cell: The cell object to get the value from. (func.__closure__ objects)
Returns:
The value from the cell or CellEmpty if a ValueError is raised.
"""
try:
return cell.cell_contents
except ValueError:
return CellEmpty
class ScanStatus(enum.Enum):
"""State of the dis instruction scanning loop."""
SCANNING = enum.auto()
GETTING_ATTR = enum.auto()
GETTING_STATE = enum.auto()
GETTING_VAR = enum.auto()
@dataclasses.dataclass
class DependencyTracker:
"""State machine for identifying state attributes that are accessed by a function."""
func: FunctionType | CodeType = dataclasses.field()
state_cls: Type[BaseState] = dataclasses.field()
dependencies: dict[str, set[str]] = dataclasses.field(default_factory=dict)
scan_status: ScanStatus = dataclasses.field(default=ScanStatus.SCANNING)
top_of_stack: str | None = dataclasses.field(default=None)
tracked_locals: dict[str, Type[BaseState]] = dataclasses.field(default_factory=dict)
_getting_state_class: Type[BaseState] | None = dataclasses.field(default=None)
_getting_var_instructions: list[dis.Instruction] = dataclasses.field(
default_factory=list
)
INVALID_NAMES: ClassVar[list[str]] = ["parent_state", "substates", "get_substate"]
def __post_init__(self):
"""After initializing, populate the dependencies dict."""
with contextlib.suppress(AttributeError):
# unbox functools.partial
self.func = cast(FunctionType, self.func.func) # pyright: ignore[reportAttributeAccessIssue]
with contextlib.suppress(AttributeError):
# unbox EventHandler
self.func = cast(FunctionType, self.func.fn) # pyright: ignore[reportAttributeAccessIssue]
if isinstance(self.func, FunctionType):
with contextlib.suppress(AttributeError, IndexError):
# the first argument to the function is the name of "self" arg
self.tracked_locals[self.func.__code__.co_varnames[0]] = self.state_cls
self._populate_dependencies()
def _merge_deps(self, tracker: DependencyTracker) -> None:
"""Merge dependencies from another DependencyTracker.
Args:
tracker: The DependencyTracker to merge dependencies from.
"""
for state_name, dep_name in tracker.dependencies.items():
self.dependencies.setdefault(state_name, set()).update(dep_name)
def load_attr_or_method(self, instruction: dis.Instruction) -> None:
"""Handle loading an attribute or method from the object on top of the stack.
This method directly tracks attributes and recursively merges
dependencies from analyzing the dependencies of any methods called.
Args:
instruction: The dis instruction to process.
Raises:
VarValueError: if the attribute is an disallowed name.
"""
from .base import ComputedVar
if instruction.argval in self.INVALID_NAMES:
raise VarValueError(
f"Cached var {self!s} cannot access arbitrary state via `{instruction.argval}`."
)
if instruction.argval == "get_state":
# Special case: arbitrary state access requested.
self.scan_status = ScanStatus.GETTING_STATE
return
if instruction.argval == "get_var_value":
# Special case: arbitrary var access requested.
self.scan_status = ScanStatus.GETTING_VAR
return
# Reset status back to SCANNING after attribute is accessed.
self.scan_status = ScanStatus.SCANNING
if not self.top_of_stack:
return
target_state = self.tracked_locals[self.top_of_stack]
try:
ref_obj = getattr(target_state, instruction.argval)
except AttributeError:
# Not found on this state class, maybe it is a dynamic attribute that will be picked up later.
ref_obj = None
if isinstance(ref_obj, property) and not isinstance(ref_obj, ComputedVar):
# recurse into property fget functions
ref_obj = ref_obj.fget
if callable(ref_obj):
# recurse into callable attributes
self._merge_deps(
type(self)(func=cast(FunctionType, ref_obj), state_cls=target_state)
)
elif (
instruction.argval in target_state.backend_vars
or instruction.argval in target_state.vars
):
# var access
self.dependencies.setdefault(target_state.get_full_name(), set()).add(
instruction.argval
)
def _get_globals(self) -> dict[str, Any]:
"""Get the globals of the function.
Returns:
The var names and values in the globals of the function.
"""
if isinstance(self.func, CodeType):
return {}
return self.func.__globals__ # pyright: ignore[reportAttributeAccessIssue]
def _get_closure(self) -> dict[str, Any]:
"""Get the closure of the function, with unbound values omitted.
Returns:
The var names and values in the closure of the function.
"""
if isinstance(self.func, CodeType):
return {}
return {
var_name: get_cell_value(cell)
for var_name, cell in zip(
self.func.__code__.co_freevars, # pyright: ignore[reportAttributeAccessIssue]
self.func.__closure__ or (),
strict=False,
)
if get_cell_value(cell) is not CellEmpty
}
def handle_getting_state(self, instruction: dis.Instruction) -> None:
"""Handle bytecode analysis when `get_state` was called in the function.
If the wrapped function is getting an arbitrary state and saving it to a
local variable, this method associates the local variable name with the
state class in self.tracked_locals.
When an attribute/method is accessed on a tracked local, it will be
associated with this state.
Args:
instruction: The dis instruction to process.
Raises:
VarValueError: if the state class cannot be determined from the instruction.
"""
from reflex.state import BaseState
if instruction.opname == "LOAD_FAST":
raise VarValueError(
f"Dependency detection cannot identify get_state class from local var {instruction.argval}."
)
if isinstance(self.func, CodeType):
raise VarValueError(
"Dependency detection cannot identify get_state class from a code object."
)
if instruction.opname == "LOAD_GLOBAL":
# Special case: referencing state class from global scope.
try:
self._getting_state_class = self._get_globals()[instruction.argval]
except (ValueError, KeyError) as ve:
raise VarValueError(
f"Cached var {self!s} cannot access arbitrary state `{instruction.argval}`, not found in globals."
) from ve
elif instruction.opname == "LOAD_DEREF":
# Special case: referencing state class from closure.
try:
self._getting_state_class = self._get_closure()[instruction.argval]
except (ValueError, KeyError) as ve:
raise VarValueError(
f"Cached var {self!s} cannot access arbitrary state `{instruction.argval}`, is it defined yet?"
) from ve
elif instruction.opname == "STORE_FAST":
# Storing the result of get_state in a local variable.
if not isinstance(self._getting_state_class, type) or not issubclass(
self._getting_state_class, BaseState
):
raise VarValueError(
f"Cached var {self!s} cannot determine dependencies in fetched state `{instruction.argval}`."
)
self.tracked_locals[instruction.argval] = self._getting_state_class
self.scan_status = ScanStatus.SCANNING
self._getting_state_class = None
def _eval_var(self) -> Var:
"""Evaluate instructions from the wrapped function to get the Var object.
Returns:
The Var object.
Raises:
VarValueError: if the source code for the var cannot be determined.
"""
# Get the original source code and eval it to get the Var.
module = inspect.getmodule(self.func)
positions0 = self._getting_var_instructions[0].positions
positions1 = self._getting_var_instructions[-1].positions
if module is None or positions0 is None or positions1 is None:
raise VarValueError(
f"Cannot determine the source code for the var in {self.func!r}."
)
start_line = positions0.lineno
start_column = positions0.col_offset
end_line = positions1.end_lineno
end_column = positions1.end_col_offset
if (
start_line is None
or start_column is None
or end_line is None
or end_column is None
):
raise VarValueError(
f"Cannot determine the source code for the var in {self.func!r}."
)
source = inspect.getsource(module).splitlines(True)[start_line - 1 : end_line]
# Create a python source string snippet.
if len(source) > 1:
snipped_source = "".join(
[
*source[0][start_column:],
*(source[1:-2] if len(source) > 2 else []),
*source[-1][: end_column - 1],
]
)
else:
snipped_source = source[0][start_column : end_column - 1]
# Evaluate the string in the context of the function's globals and closure.
return eval(f"({snipped_source})", self._get_globals(), self._get_closure())
def handle_getting_var(self, instruction: dis.Instruction) -> None:
"""Handle bytecode analysis when `get_var_value` was called in the function.
This only really works if the expression passed to `get_var_value` is
evaluable in the function's global scope or closure, so getting the var
value from a var saved in a local variable or in the class instance is
not possible.
Args:
instruction: The dis instruction to process.
Raises:
VarValueError: if the source code for the var cannot be determined.
"""
if instruction.opname == "CALL" and self._getting_var_instructions:
if self._getting_var_instructions:
the_var = self._eval_var()
the_var_data = the_var._get_all_var_data()
if the_var_data is None:
raise VarValueError(
f"Cannot determine the source code for the var in {self.func!r}."
)
self.dependencies.setdefault(the_var_data.state, set()).add(
the_var_data.field_name
)
self._getting_var_instructions.clear()
self.scan_status = ScanStatus.SCANNING
else:
self._getting_var_instructions.append(instruction)
def _populate_dependencies(self) -> None:
"""Update self.dependencies based on the disassembly of self.func.
Save references to attributes accessed on "self" or other fetched states.
Recursively called when the function makes a method call on "self" or
define comprehensions or nested functions that may reference "self".
"""
for instruction in dis.get_instructions(self.func):
if self.scan_status == ScanStatus.GETTING_STATE:
self.handle_getting_state(instruction)
elif self.scan_status == ScanStatus.GETTING_VAR:
self.handle_getting_var(instruction)
elif (
instruction.opname in ("LOAD_FAST", "LOAD_DEREF")
and instruction.argval in self.tracked_locals
):
# bytecode loaded the class instance to the top of stack, next load instruction
# is referencing an attribute on self
self.top_of_stack = instruction.argval
self.scan_status = ScanStatus.GETTING_ATTR
elif self.scan_status == ScanStatus.GETTING_ATTR and instruction.opname in (
"LOAD_ATTR",
"LOAD_METHOD",
):
self.load_attr_or_method(instruction)
self.top_of_stack = None
elif instruction.opname == "LOAD_CONST" and isinstance(
instruction.argval, CodeType
):
# recurse into nested functions / comprehensions, which can reference
# instance attributes from the outer scope
self._merge_deps(
type(self)(
func=instruction.argval,
state_cls=self.state_cls,
tracked_locals=self.tracked_locals,
)
)

View File

@ -53,8 +53,11 @@ from .number import (
)
if TYPE_CHECKING:
from .base import BASE_TYPE, DATACLASS_TYPE, SQLA_TYPE
from .function import FunctionVar
from .object import ObjectVar
STRING_TYPE = TypeVar("STRING_TYPE", default=str)
@ -961,6 +964,24 @@ class ArrayVar(Var[ARRAY_VAR_TYPE], python_types=(list, tuple, set)):
i: int | NumberVar,
) -> ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]: ...
@overload
def __getitem__(
self: ARRAY_VAR_OF_LIST_ELEMENT[BASE_TYPE],
i: int | NumberVar,
) -> ObjectVar[BASE_TYPE]: ...
@overload
def __getitem__(
self: ARRAY_VAR_OF_LIST_ELEMENT[SQLA_TYPE],
i: int | NumberVar,
) -> ObjectVar[SQLA_TYPE]: ...
@overload
def __getitem__(
self: ARRAY_VAR_OF_LIST_ELEMENT[DATACLASS_TYPE],
i: int | NumberVar,
) -> ObjectVar[DATACLASS_TYPE]: ...
@overload
def __getitem__(self, i: int | NumberVar) -> Var: ...
@ -1648,10 +1669,6 @@ def repeat_array_operation(
)
if TYPE_CHECKING:
from .function import FunctionVar
@var_operation
def map_array_operation(
array: ArrayVar[ARRAY_VAR_TYPE],

View File

@ -136,9 +136,9 @@ def _assert_token(connection_banner, driver):
driver: Selenium webdriver instance.
"""
ss = SessionStorage(driver)
assert connection_banner._poll_for(
lambda: ss.get("token") is not None
), "token not found"
assert connection_banner._poll_for(lambda: ss.get("token") is not None), (
"token not found"
)
@pytest.mark.asyncio
@ -153,7 +153,6 @@ async def test_connection_banner(connection_banner: AppHarness):
driver = connection_banner.frontend()
_assert_token(connection_banner, driver)
assert connection_banner._poll_for(lambda: not has_error_modal(driver))
delay_button = driver.find_element(By.ID, "delay")

View File

@ -605,6 +605,20 @@ def VarOperations():
rx.box(rx.foreach(range(42, 80, 3), rx.text.span), id="range_in_foreach2"),
rx.box(rx.foreach(range(42, 20, -6), rx.text.span), id="range_in_foreach3"),
rx.box(rx.foreach(range(42, 43, 5), rx.text.span), id="range_in_foreach4"),
# Literal dict in a foreach
rx.box(rx.foreach({"a": 1, "b": 2}, rx.text.span), id="dict_in_foreach1"),
# State Var dict in a foreach
rx.box(
rx.foreach(VarOperationState.dict1, rx.text.span),
id="dict_in_foreach2",
),
rx.box(
rx.foreach(
VarOperationState.dict1.merge(VarOperationState.dict2),
rx.text.span,
),
id="dict_in_foreach3",
),
)
@ -809,6 +823,9 @@ def test_var_operations(driver, var_operations: AppHarness):
("range_in_foreach2", "42454851545760636669727578"),
("range_in_foreach3", "42363024"),
("range_in_foreach4", "42"),
("dict_in_foreach1", "a1b2"),
("dict_in_foreach2", "12"),
("dict_in_foreach3", "1234"),
]
for tag, expected in tests:

View File

@ -3,7 +3,7 @@
from typing import Generator
import pytest
from playwright.sync_api import Page
from playwright.sync_api import Page, expect
from reflex.testing import AppHarness
@ -87,12 +87,14 @@ def test_table(page: Page, table_app: AppHarness):
table = page.get_by_role("table")
# Check column headers
headers = table.get_by_role("columnheader").all_inner_texts()
assert headers == expected_col_headers
headers = table.get_by_role("columnheader")
for header, exp_value in zip(headers.all(), expected_col_headers, strict=True):
expect(header).to_have_text(exp_value)
# Check rows headers
rows = table.get_by_role("rowheader").all_inner_texts()
assert rows == expected_row_headers
rows = table.get_by_role("rowheader")
for row, expected_row in zip(rows.all(), expected_row_headers, strict=True):
expect(row).to_have_text(expected_row)
# Check cells
rows = table.get_by_role("cell").all_inner_texts()

View File

@ -55,13 +55,13 @@ def create_color_var(color):
Color,
),
(
create_color_var(f'{rx.color(ColorState.color, f"{ColorState.shade}")}'), # pyright: ignore [reportArgumentType]
create_color_var(f"{rx.color(ColorState.color, f'{ColorState.shade}')}"), # pyright: ignore [reportArgumentType]
f'("var(--"+{color_state_name!s}.color+"-"+{color_state_name!s}.shade+")")',
str,
),
(
create_color_var(
f'{rx.color(f"{ColorState.color}", f"{ColorState.shade}")}' # pyright: ignore [reportArgumentType]
f"{rx.color(f'{ColorState.color}', f'{ColorState.shade}')}" # pyright: ignore [reportArgumentType]
),
f'("var(--"+{color_state_name!s}.color+"-"+{color_state_name!s}.shade+")")',
str,

View File

@ -170,32 +170,32 @@ seen_index_vars = set()
ForEachState.primary_color,
display_primary_colors,
{
"iterable_state": f"{ForEachState.get_full_name()}.primary_color",
"iterable_type": "dict",
"iterable_state": f"Object.entries({ForEachState.get_full_name()}.primary_color)",
"iterable_type": "list",
},
),
(
ForEachState.color_with_shades,
display_color_with_shades,
{
"iterable_state": f"{ForEachState.get_full_name()}.color_with_shades",
"iterable_type": "dict",
"iterable_state": f"Object.entries({ForEachState.get_full_name()}.color_with_shades)",
"iterable_type": "list",
},
),
(
ForEachState.nested_colors_with_shades,
display_nested_color_with_shades,
{
"iterable_state": f"{ForEachState.get_full_name()}.nested_colors_with_shades",
"iterable_type": "dict",
"iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades)",
"iterable_type": "list",
},
),
(
ForEachState.nested_colors_with_shades,
display_nested_color_with_shades_v2,
{
"iterable_state": f"{ForEachState.get_full_name()}.nested_colors_with_shades",
"iterable_type": "dict",
"iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades)",
"iterable_type": "list",
},
),
(

View File

@ -277,9 +277,9 @@ def test_add_page_set_route_dynamic(index_page, windows_platform: bool):
assert app._pages.keys() == {"test/[dynamic]"}
assert "dynamic" in app._state.computed_vars
assert app._state.computed_vars["dynamic"]._deps(objclass=EmptyState) == {
constants.ROUTER
EmptyState.get_full_name(): {constants.ROUTER},
}
assert constants.ROUTER in app._state()._computed_var_dependencies
assert constants.ROUTER in app._state()._var_dependencies
def test_add_page_set_route_nested(app: App, index_page, windows_platform: bool):
@ -995,9 +995,9 @@ async def test_dynamic_route_var_route_change_completed_on_load(
assert arg_name in app._state.vars
assert arg_name in app._state.computed_vars
assert app._state.computed_vars[arg_name]._deps(objclass=DynamicState) == {
constants.ROUTER
DynamicState.get_full_name(): {constants.ROUTER},
}
assert constants.ROUTER in app._state()._computed_var_dependencies
assert constants.ROUTER in app._state()._var_dependencies
substate_token = _substate_key(token, DynamicState)
sid = "mock_sid"
@ -1274,12 +1274,23 @@ def compilable_app(tmp_path) -> Generator[tuple[App, Path], None, None]:
yield app, web_dir
def test_app_wrap_compile_theme(compilable_app: tuple[App, Path]):
@pytest.mark.parametrize(
"react_strict_mode",
[True, False],
)
def test_app_wrap_compile_theme(
react_strict_mode: bool, compilable_app: tuple[App, Path], mocker
):
"""Test that the radix theme component wraps the app.
Args:
react_strict_mode: Whether to use React Strict Mode.
compilable_app: compilable_app fixture.
mocker: pytest mocker object.
"""
conf = rx.Config(app_name="testing", react_strict_mode=react_strict_mode)
mocker.patch("reflex.config._get_config", return_value=conf)
app, web_dir = compilable_app
app.theme = rx.theme(accent_color="plum")
app._compile()
@ -1290,24 +1301,37 @@ def test_app_wrap_compile_theme(compilable_app: tuple[App, Path]):
assert (
"function AppWrap({children}) {"
"return ("
"<RadixThemesColorModeProvider>"
+ ("<StrictMode>" if react_strict_mode else "")
+ "<RadixThemesColorModeProvider>"
"<RadixThemesTheme accentColor={\"plum\"} css={{...theme.styles.global[':root'], ...theme.styles.global.body}}>"
"<Fragment>"
"{children}"
"</Fragment>"
"</RadixThemesTheme>"
"</RadixThemesColorModeProvider>"
")"
+ ("</StrictMode>" if react_strict_mode else "")
+ ")"
"}"
) in "".join(app_js_lines)
def test_app_wrap_priority(compilable_app: tuple[App, Path]):
@pytest.mark.parametrize(
"react_strict_mode",
[True, False],
)
def test_app_wrap_priority(
react_strict_mode: bool, compilable_app: tuple[App, Path], mocker
):
"""Test that the app wrap components are wrapped in the correct order.
Args:
react_strict_mode: Whether to use React Strict Mode.
compilable_app: compilable_app fixture.
mocker: pytest mocker object.
"""
conf = rx.Config(app_name="testing", react_strict_mode=react_strict_mode)
mocker.patch("reflex.config._get_config", return_value=conf)
app, web_dir = compilable_app
class Fragment1(Component):
@ -1339,8 +1363,7 @@ def test_app_wrap_priority(compilable_app: tuple[App, Path]):
]
assert (
"function AppWrap({children}) {"
"return ("
"<RadixThemesBox>"
"return (" + ("<StrictMode>" if react_strict_mode else "") + "<RadixThemesBox>"
'<RadixThemesText as={"p"}>'
"<RadixThemesColorModeProvider>"
"<Fragment2>"
@ -1350,8 +1373,7 @@ def test_app_wrap_priority(compilable_app: tuple[App, Path]):
"</Fragment2>"
"</RadixThemesColorModeProvider>"
"</RadixThemesText>"
"</RadixThemesBox>"
")"
"</RadixThemesBox>" + ("</StrictMode>" if react_strict_mode else "") + ")"
"}"
) in "".join(app_js_lines)
@ -1555,6 +1577,16 @@ def test_app_with_valid_var_dependencies(compilable_app: tuple[App, Path]):
def bar(self) -> str:
return "bar"
class Child1(ValidDepState):
@computed_var(deps=["base", ValidDepState.bar])
def other(self) -> str:
return "other"
class Child2(ValidDepState):
@computed_var(deps=["base", Child1.other])
def other(self) -> str:
return "other"
app._state = ValidDepState
app._compile()

View File

@ -32,7 +32,7 @@ runner = CliRunner()
app_name="test",
),
False,
'module.exports = {basePath: "", compress: true, reactStrictMode: true, trailingSlash: true, staticPageGenerationTimeout: 60};',
'module.exports = {basePath: "", compress: true, trailingSlash: true, staticPageGenerationTimeout: 60};',
),
(
Config(
@ -40,7 +40,7 @@ runner = CliRunner()
static_page_generation_timeout=30,
),
False,
'module.exports = {basePath: "", compress: true, reactStrictMode: true, trailingSlash: true, staticPageGenerationTimeout: 30};',
'module.exports = {basePath: "", compress: true, trailingSlash: true, staticPageGenerationTimeout: 30};',
),
(
Config(
@ -48,7 +48,7 @@ runner = CliRunner()
next_compression=False,
),
False,
'module.exports = {basePath: "", compress: false, reactStrictMode: true, trailingSlash: true, staticPageGenerationTimeout: 60};',
'module.exports = {basePath: "", compress: false, trailingSlash: true, staticPageGenerationTimeout: 60};',
),
(
Config(
@ -56,7 +56,7 @@ runner = CliRunner()
frontend_path="/test",
),
False,
'module.exports = {basePath: "/test", compress: true, reactStrictMode: true, trailingSlash: true, staticPageGenerationTimeout: 60};',
'module.exports = {basePath: "/test", compress: true, trailingSlash: true, staticPageGenerationTimeout: 60};',
),
(
Config(
@ -65,14 +65,14 @@ runner = CliRunner()
next_compression=False,
),
False,
'module.exports = {basePath: "/test", compress: false, reactStrictMode: true, trailingSlash: true, staticPageGenerationTimeout: 60};',
'module.exports = {basePath: "/test", compress: false, trailingSlash: true, staticPageGenerationTimeout: 60};',
),
(
Config(
app_name="test",
),
True,
'module.exports = {basePath: "", compress: true, reactStrictMode: true, trailingSlash: true, staticPageGenerationTimeout: 60, output: "export", distDir: "_static"};',
'module.exports = {basePath: "", compress: true, trailingSlash: true, staticPageGenerationTimeout: 60, output: "export", distDir: "_static"};',
),
],
)

View File

@ -14,6 +14,7 @@ from typing import (
Any,
AsyncGenerator,
Callable,
ClassVar,
Dict,
List,
Optional,
@ -1169,13 +1170,17 @@ def test_conditional_computed_vars():
ms = MainState()
# Initially there are no dirty computed vars.
assert ms._dirty_computed_vars(from_vars={"flag"}) == {"rendered_var"}
assert ms._dirty_computed_vars(from_vars={"t2"}) == {"rendered_var"}
assert ms._dirty_computed_vars(from_vars={"t1"}) == {"rendered_var"}
assert ms._dirty_computed_vars(from_vars={"flag"}) == {
(MainState.get_full_name(), "rendered_var")
}
assert ms._dirty_computed_vars(from_vars={"t2"}) == {
(MainState.get_full_name(), "rendered_var")
}
assert ms._dirty_computed_vars(from_vars={"t1"}) == {
(MainState.get_full_name(), "rendered_var")
}
assert ms.computed_vars["rendered_var"]._deps(objclass=MainState) == {
"flag",
"t1",
"t2",
MainState.get_full_name(): {"flag", "t1", "t2"}
}
@ -1370,7 +1375,10 @@ def test_cached_var_depends_on_event_handler(use_partial: bool):
assert isinstance(HandlerState.handler, EventHandler)
s = HandlerState()
assert "cached_x_side_effect" in s._computed_var_dependencies["x"]
assert (
HandlerState.get_full_name(),
"cached_x_side_effect",
) in s._var_dependencies["x"]
assert s.cached_x_side_effect == 1
assert s.x == 43
s.handler()
@ -1460,15 +1468,15 @@ def test_computed_var_dependencies():
return [z in self._z for z in range(5)]
cs = ComputedState()
assert cs._computed_var_dependencies["v"] == {
"comp_v",
"comp_v_backend",
"comp_v_via_property",
assert cs._var_dependencies["v"] == {
(ComputedState.get_full_name(), "comp_v"),
(ComputedState.get_full_name(), "comp_v_backend"),
(ComputedState.get_full_name(), "comp_v_via_property"),
}
assert cs._computed_var_dependencies["w"] == {"comp_w"}
assert cs._computed_var_dependencies["x"] == {"comp_x"}
assert cs._computed_var_dependencies["y"] == {"comp_y"}
assert cs._computed_var_dependencies["_z"] == {"comp_z"}
assert cs._var_dependencies["w"] == {(ComputedState.get_full_name(), "comp_w")}
assert cs._var_dependencies["x"] == {(ComputedState.get_full_name(), "comp_x")}
assert cs._var_dependencies["y"] == {(ComputedState.get_full_name(), "comp_y")}
assert cs._var_dependencies["_z"] == {(ComputedState.get_full_name(), "comp_z")}
def test_backend_method():
@ -1615,7 +1623,7 @@ async def test_state_with_invalid_yield(capsys, mock_app):
id="backend_error",
position="top-center",
style={"width": "500px"},
) # pyright: ignore [reportCallIssue, reportArgumentType]
)
],
token="",
)
@ -3180,7 +3188,7 @@ async def test_get_state_from_sibling_not_cached(mock_app: rx.App, token: str):
RxState = State
def test_potentially_dirty_substates():
def test_potentially_dirty_states():
"""Test that potentially_dirty_substates returns the correct substates.
Even if the name "State" is shadowed, it should still work correctly.
@ -3196,13 +3204,19 @@ def test_potentially_dirty_substates():
def bar(self) -> str:
return ""
assert RxState._potentially_dirty_substates() == set()
assert State._potentially_dirty_substates() == set()
assert C1._potentially_dirty_substates() == set()
assert RxState._get_potentially_dirty_states() == set()
assert State._get_potentially_dirty_states() == set()
assert C1._get_potentially_dirty_states() == set()
def test_router_var_dep() -> None:
"""Test that router var dependencies are correctly tracked."""
@pytest.mark.asyncio
async def test_router_var_dep(state_manager: StateManager, token: str) -> None:
"""Test that router var dependencies are correctly tracked.
Args:
state_manager: A state manager.
token: A token.
"""
class RouterVarParentState(State):
"""A parent state for testing router var dependency."""
@ -3219,30 +3233,27 @@ def test_router_var_dep() -> None:
foo = RouterVarDepState.computed_vars["foo"]
State._init_var_dependency_dicts()
assert foo._deps(objclass=RouterVarDepState) == {"router"}
assert RouterVarParentState._potentially_dirty_substates() == {RouterVarDepState}
assert RouterVarParentState._substate_var_dependencies == {
"router": {RouterVarDepState.get_name()}
}
assert RouterVarDepState._computed_var_dependencies == {
"router": {"foo"},
assert foo._deps(objclass=RouterVarDepState) == {
RouterVarDepState.get_full_name(): {"router"}
}
assert (RouterVarDepState.get_full_name(), "foo") in State._var_dependencies[
"router"
]
rx_state = State()
parent_state = RouterVarParentState()
state = RouterVarDepState()
# link states
rx_state.substates = {RouterVarParentState.get_name(): parent_state}
parent_state.parent_state = rx_state
state.parent_state = parent_state
parent_state.substates = {RouterVarDepState.get_name(): state}
# Get state from state manager.
state_manager.state = State
rx_state = await state_manager.get_state(_substate_key(token, State))
assert RouterVarParentState.get_name() in rx_state.substates
parent_state = rx_state.substates[RouterVarParentState.get_name()]
assert RouterVarDepState.get_name() in parent_state.substates
state = parent_state.substates[RouterVarDepState.get_name()]
assert state.dirty_vars == set()
# Reassign router var
state.router = state.router
assert state.dirty_vars == {"foo", "router"}
assert rx_state.dirty_vars == {"router"}
assert state.dirty_vars == {"foo"}
assert parent_state.dirty_substates == {RouterVarDepState.get_name()}
@ -3801,3 +3812,128 @@ async def test_get_var_value(state_manager: StateManager, substate_token: str):
# Generic Var with no state
with pytest.raises(UnretrievableVarValueError):
await state.get_var_value(rx.Var("undefined"))
@pytest.mark.asyncio
async def test_async_computed_var_get_state(mock_app: rx.App, token: str):
"""A test where an async computed var depends on a var in another state.
Args:
mock_app: An app that will be returned by `get_app()`
token: A token.
"""
class Parent(BaseState):
"""A root state like rx.State."""
parent_var: int = 0
class Child2(Parent):
"""An unconnected child state."""
pass
class Child3(Parent):
"""A child state with a computed var causing it to be pre-fetched.
If child3_var gets set to a value, and `get_state` erroneously
re-fetches it from redis, the value will be lost.
"""
child3_var: int = 0
@rx.var(cache=True)
def v(self) -> int:
return self.child3_var
class Child(Parent):
"""A state simulating UpdateVarsInternalState."""
@rx.var(cache=True)
async def v(self) -> int:
p = await self.get_state(Parent)
child3 = await self.get_state(Child3)
return child3.child3_var + p.parent_var
mock_app.state_manager.state = mock_app._state = Parent
# Get the top level state via unconnected sibling.
root = await mock_app.state_manager.get_state(_substate_key(token, Child))
# Set value in parent_var to assert it does not get refetched later.
root.parent_var = 1
if isinstance(mock_app.state_manager, StateManagerRedis):
# When redis is used, only states with uncached computed vars are pre-fetched.
assert Child2.get_name() not in root.substates
assert Child3.get_name() not in root.substates
# Get the unconnected sibling state, which will be used to `get_state` other instances.
child = root.get_substate(Child.get_full_name().split("."))
# Get an uncached child state.
child2 = await child.get_state(Child2)
assert child2.parent_var == 1
# Set value on already-cached Child3 state (prefetched because it has a Computed Var).
child3 = await child.get_state(Child3)
child3.child3_var = 1
assert await child.v == 2
assert await child.v == 2
root.parent_var = 2
assert await child.v == 3
class Table(rx.ComponentState):
"""A table state."""
data: ClassVar[Var]
@rx.var(cache=True, auto_deps=False)
async def rows(self) -> List[Dict[str, Any]]:
"""Computed var over the given rows.
Returns:
The data rows.
"""
return await self.get_var_value(self.data)
@classmethod
def get_component(cls, data: Var) -> rx.Component:
"""Get the component for the table.
Args:
data: The data var.
Returns:
The component.
"""
cls.data = data
cls.computed_vars["rows"].add_dependency(cls, data)
return rx.foreach(data, lambda d: rx.text(d.to_string()))
@pytest.mark.asyncio
async def test_async_computed_var_get_var_value(mock_app: rx.App, token: str):
"""A test where an async computed var depends on a var in another state.
Args:
mock_app: An app that will be returned by `get_app()`
token: A token.
"""
class OtherState(rx.State):
"""A state with a var."""
data: List[Dict[str, Any]] = [{"foo": "bar"}]
mock_app.state_manager.state = mock_app._state = rx.State
comp = Table.create(data=OtherState.data)
state = await mock_app.state_manager.get_state(_substate_key(token, OtherState))
other_state = await state.get_state(OtherState)
assert comp.State is not None
comp_state = await state.get_state(comp.State)
assert comp_state.dirty_vars == set()
other_state.data.append({"foo": "baz"})
assert "rows" in comp_state.dirty_vars

View File

@ -1807,9 +1807,9 @@ def cv_fget(state: BaseState) -> int:
@pytest.mark.parametrize(
"deps,expected",
[
(["a"], {"a"}),
(["b"], {"b"}),
([ComputedVar(fget=cv_fget)], {"cv_fget"}),
(["a"], {None: {"a"}}),
(["b"], {None: {"b"}}),
([ComputedVar(fget=cv_fget)], {None: {"cv_fget"}}),
],
)
def test_computed_var_deps(deps: List[Union[str, Var]], expected: Set[str]):
@ -1857,6 +1857,28 @@ def test_to_string_operation():
assert single_var._var_type == Email
@pytest.mark.asyncio
async def test_async_computed_var():
side_effect_counter = 0
class AsyncComputedVarState(BaseState):
v: int = 1
@computed_var(cache=True)
async def async_computed_var(self) -> int:
nonlocal side_effect_counter
side_effect_counter += 1
return self.v + 1
my_state = AsyncComputedVarState()
assert await my_state.async_computed_var == 2
assert await my_state.async_computed_var == 2
my_state.v = 2
assert await my_state.async_computed_var == 3
assert await my_state.async_computed_var == 3
assert side_effect_counter == 2
def test_var_data_hooks():
var_data_str = VarData(hooks="what")
var_data_list = VarData(hooks=["what"])

View File

@ -123,6 +123,7 @@ def test_validate_invalid_bun_path(mocker):
mocker: Pytest mocker object.
"""
mock_path = mocker.Mock()
mock_path.samefile.return_value = False
mocker.patch("reflex.utils.path_ops.get_bun_path", return_value=mock_path)
mocker.patch("reflex.utils.prerequisites.get_bun_version", return_value=None)
@ -138,6 +139,7 @@ def test_validate_bun_path_incompatible_version(mocker):
mocker: Pytest mocker object.
"""
mock_path = mocker.Mock()
mock_path.samefile.return_value = False
mocker.patch("reflex.utils.path_ops.get_bun_path", return_value=mock_path)
mocker.patch(
"reflex.utils.prerequisites.get_bun_version",

View File

@ -2,7 +2,8 @@ from typing import List, Mapping, Union
import pytest
from reflex.vars.base import figure_out_type
from reflex.state import State
from reflex.vars.base import computed_var, figure_out_type
class CustomDict(dict[str, str]):
@ -47,3 +48,16 @@ class ChildGenericDict(GenericDict):
)
def test_figure_out_type(value, expected):
assert figure_out_type(value) == expected
def test_computed_var_replace() -> None:
class StateTest(State):
@computed_var(cache=True)
def cv(self) -> int:
return 1
cv = StateTest.cv
assert cv._var_type is int
replaced = cv._replace(_var_type=float)
assert replaced._var_type is float

View File

@ -1,10 +1,14 @@
import dataclasses
import pytest
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column
from typing_extensions import assert_type
import reflex as rx
from reflex.utils.types import GenericType
from reflex.vars.base import Var
from reflex.vars.object import LiteralObjectVar, ObjectVar
from reflex.vars.sequence import ArrayVar
class Bare:
@ -32,14 +36,44 @@ class Base(rx.Base):
quantity: int = 0
class SqlaBase(DeclarativeBase, MappedAsDataclass):
"""Sqlalchemy declarative mapping base class."""
pass
class SqlaModel(SqlaBase):
"""A sqlalchemy model with a single attribute."""
__tablename__: str = "sqla_model"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True, init=False)
quantity: Mapped[int] = mapped_column(default=0)
@dataclasses.dataclass
class Dataclass:
"""A dataclass with a single attribute."""
quantity: int = 0
class ObjectState(rx.State):
"""A reflex state with bare and base objects."""
"""A reflex state with bare, base and sqlalchemy base vars."""
bare: rx.Field[Bare] = rx.field(Bare())
bare_optional: rx.Field[Bare | None] = rx.field(None)
base: rx.Field[Base] = rx.field(Base())
base_optional: rx.Field[Base | None] = rx.field(None)
sqlamodel: rx.Field[SqlaModel] = rx.field(SqlaModel())
sqlamodel_optional: rx.Field[SqlaModel | None] = rx.field(None)
dataclass: rx.Field[Dataclass] = rx.field(Dataclass())
dataclass_optional: rx.Field[Dataclass | None] = rx.field(None)
base_list: rx.Field[list[Base]] = rx.field([Base()])
@pytest.mark.parametrize("type_", [Base, Bare])
@pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass])
def test_var_create(type_: GenericType) -> None:
my_object = type_()
var = Var.create(my_object)
@ -49,7 +83,7 @@ def test_var_create(type_: GenericType) -> None:
assert quantity._var_type is int
@pytest.mark.parametrize("type_", [Base, Bare])
@pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass])
def test_literal_create(type_: GenericType) -> None:
my_object = type_()
var = LiteralObjectVar.create(my_object)
@ -59,7 +93,7 @@ def test_literal_create(type_: GenericType) -> None:
assert quantity._var_type is int
@pytest.mark.parametrize("type_", [Base, Bare])
@pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass])
def test_guess(type_: GenericType) -> None:
my_object = type_()
var = Var.create(my_object)
@ -70,7 +104,7 @@ def test_guess(type_: GenericType) -> None:
assert quantity._var_type is int
@pytest.mark.parametrize("type_", [Base, Bare])
@pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass])
def test_state(type_: GenericType) -> None:
attr_name = type_.__name__.lower()
var = getattr(ObjectState, attr_name)
@ -80,7 +114,7 @@ def test_state(type_: GenericType) -> None:
assert quantity._var_type is int
@pytest.mark.parametrize("type_", [Base, Bare])
@pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass])
def test_state_to_operation(type_: GenericType) -> None:
attr_name = type_.__name__.lower()
original_var = getattr(ObjectState, attr_name)
@ -100,3 +134,29 @@ def test_typing() -> None:
# Base
var = ObjectState.base
_ = assert_type(var, ObjectVar[Base])
optional_var = ObjectState.base_optional
_ = assert_type(optional_var, ObjectVar[Base | None])
list_var = ObjectState.base_list
_ = assert_type(list_var, ArrayVar[list[Base]])
list_var_0 = list_var[0]
_ = assert_type(list_var_0, ObjectVar[Base])
# Sqla
var = ObjectState.sqlamodel
_ = assert_type(var, ObjectVar[SqlaModel])
optional_var = ObjectState.sqlamodel_optional
_ = assert_type(optional_var, ObjectVar[SqlaModel | None])
list_var = ObjectState.base_list
_ = assert_type(list_var, ArrayVar[list[Base]])
list_var_0 = list_var[0]
_ = assert_type(list_var_0, ObjectVar[Base])
# Dataclass
var = ObjectState.dataclass
_ = assert_type(var, ObjectVar[Dataclass])
optional_var = ObjectState.dataclass_optional
_ = assert_type(optional_var, ObjectVar[Dataclass | None])
list_var = ObjectState.base_list
_ = assert_type(list_var, ArrayVar[list[Base]])
list_var_0 = list_var[0]
_ = assert_type(list_var_0, ObjectVar[Base])