Browse Source

Merge branch 'main' into add-validation-to-function-vars

Khaleel Al-Adhami 4 months ago
parent
commit
68b8c12127
77 changed files with 1071 additions and 1018 deletions
  1. 4 10
      .github/workflows/benchmarks.yml
  2. 1 1
      .github/workflows/check_outdated_dependencies.yml
  3. 2 2
      .github/workflows/integration_app_harness.yml
  4. 24 9
      .github/workflows/integration_tests.yml
  5. 3 7
      .github/workflows/unit_tests.yml
  6. 1 1
      .pre-commit-config.yaml
  7. 2 2
      CONTRIBUTING.md
  8. 1 1
      README.md
  9. 1 1
      docs/de/README.md
  10. 1 1
      docs/es/README.md
  11. 1 1
      docs/in/README.md
  12. 1 1
      docs/it/README.md
  13. 1 1
      docs/ja/README.md
  14. 1 1
      docs/kr/README.md
  15. 1 1
      docs/pe/README.md
  16. 1 1
      docs/pt/pt_br/README.md
  17. 1 1
      docs/tr/README.md
  18. 1 1
      docs/vi/README.md
  19. 1 1
      docs/zh/zh_cn/README.md
  20. 1 1
      docs/zh/zh_tw/README.md
  21. 218 281
      poetry.lock
  22. 4 5
      pyproject.toml
  23. 1 1
      reflex/.templates/jinja/custom_components/pyproject.toml.jinja2
  24. 2 0
      reflex/.templates/web/utils/state.js
  25. 0 16
      reflex/__init__.py
  26. 0 1
      reflex/__init__.pyi
  27. 42 14
      reflex/app.py
  28. 3 4
      reflex/app_module_for_backend.py
  29. 19 56
      reflex/components/component.py
  30. 3 12
      reflex/components/datadisplay/code.py
  31. 2 2
      reflex/components/el/elements/forms.py
  32. 4 4
      reflex/components/el/elements/forms.pyi
  33. 38 12
      reflex/components/lucide/icon.py
  34. 50 0
      reflex/components/lucide/icon.pyi
  35. 6 6
      reflex/components/radix/primitives/form.pyi
  36. 2 0
      reflex/components/recharts/__init__.py
  37. 2 0
      reflex/components/recharts/__init__.pyi
  38. 7 5
      reflex/components/recharts/charts.py
  39. 19 4
      reflex/components/recharts/general.py
  40. 55 4
      reflex/components/recharts/general.pyi
  41. 27 3
      reflex/config.py
  42. 1 0
      reflex/constants/__init__.py
  43. 1 0
      reflex/constants/base.py
  44. 5 3
      reflex/custom_components/custom_components.py
  45. 65 96
      reflex/event.py
  46. 0 2
      reflex/experimental/__init__.py
  47. 0 37
      reflex/experimental/assets.py
  48. 34 12
      reflex/reflex.py
  49. 19 21
      reflex/state.py
  50. 3 1
      reflex/style.py
  51. 2 0
      reflex/testing.py
  52. 5 1
      reflex/utils/build.py
  53. 3 11
      reflex/utils/console.py
  54. 58 16
      reflex/utils/exceptions.py
  55. 36 47
      reflex/utils/exec.py
  56. 6 1
      reflex/utils/export.py
  57. 0 32
      reflex/utils/format.py
  58. 3 3
      reflex/utils/path_ops.py
  59. 82 38
      reflex/utils/prerequisites.py
  60. 1 2
      reflex/utils/processes.py
  61. 3 2
      reflex/utils/telemetry.py
  62. 1 3
      reflex/utils/types.py
  63. 29 61
      reflex/vars/base.py
  64. 44 33
      reflex/vars/object.py
  65. 1 1
      tests/integration/init-test/Dockerfile
  66. 25 23
      tests/integration/test_call_script.py
  67. 6 6
      tests/integration/test_client_storage.py
  68. 2 1
      tests/integration/test_connection_banner.py
  69. 0 13
      tests/units/assets/test_assets.py
  70. 6 8
      tests/units/components/datadisplay/conftest.py
  71. 5 4
      tests/units/components/datadisplay/test_datatable.py
  72. 49 45
      tests/units/components/test_component.py
  73. 3 4
      tests/units/test_event.py
  74. 5 5
      tests/units/test_state.py
  75. 2 2
      tests/units/test_style.py
  76. 7 4
      tests/units/test_var.py
  77. 5 5
      tests/units/vars/test_base.py

+ 4 - 10
.github/workflows/benchmarks.yml

@@ -81,24 +81,18 @@ jobs:
       matrix:
         # Show OS combos first in GUI
         os: [ubuntu-latest, windows-latest, macos-latest]
-        python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8"]
+        python-version: ['3.10.16', '3.11.11', '3.12.8']
         exclude:
           - os: windows-latest
-            python-version: "3.10.16"
-          - os: windows-latest
-            python-version: "3.9.21"
+            python-version: '3.10.16'
           # keep only one python version for MacOS
           - os: macos-latest
-            python-version: "3.9.21"
-          - os: macos-latest
-            python-version: "3.10.16"
+            python-version: '3.10.16'
           - os: macos-latest
             python-version: "3.11.11"
         include:
           - os: windows-latest
-            python-version: "3.10.11"
-          - os: windows-latest
-            python-version: "3.9.13"
+            python-version: '3.10.11'
 
     runs-on: ${{ matrix.os }}
     steps:

+ 1 - 1
.github/workflows/check_outdated_dependencies.yml

@@ -16,7 +16,7 @@ jobs:
 
       - uses: ./.github/actions/setup_build_env
         with:
-          python-version: "3.9.21"
+          python-version: '3.10'
           run-poetry-install: true
           create-venv-at-path: .venv
 

+ 2 - 2
.github/workflows/integration_app_harness.yml

@@ -47,14 +47,14 @@ jobs:
           python-version: ${{ matrix.python-version }}
           run-poetry-install: true
           create-venv-at-path: .venv
-      - run: poetry run uv pip install pyvirtualdisplay pillow pytest-split
+      - run: poetry run uv pip install pyvirtualdisplay pillow pytest-split pytest-retry
       - name: Run app harness tests
         env:
           SCREENSHOT_DIR: /tmp/screenshots/${{ matrix.state_manager }}/${{ matrix.python-version }}/${{ matrix.split_index }}
           REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
         run: |
           poetry run playwright install chromium
-          poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}}
+          poetry run pytest tests/integration --retries 3 --maxfail=5 --splits 2 --group ${{matrix.split_index}}
       - uses: actions/upload-artifact@v4
         name: Upload failed test screenshots
         if: always()

+ 24 - 9
.github/workflows/integration_tests.yml

@@ -33,7 +33,7 @@ env:
   PR_TITLE: ${{ github.event.pull_request.title }}
 
 jobs:
-  example-counter:
+  example-counter-and-nba-proxy:
     env:
       OUTPUT_FILE: import_benchmark.json
     timeout-minutes: 30
@@ -43,22 +43,17 @@ jobs:
       matrix:
         # Show OS combos first in GUI
         os: [ubuntu-latest, windows-latest]
-        python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
-        # Windows is a bit behind on Python version availability in Github
+        python-version: ['3.10.16', '3.11.11', '3.12.8', '3.13.1']
         exclude:
           - os: windows-latest
             python-version: "3.11.11"
           - os: windows-latest
-            python-version: "3.10.16"
-          - os: windows-latest
-            python-version: "3.9.21"
+            python-version: '3.10.16'
         include:
           - os: windows-latest
             python-version: "3.11.9"
           - os: windows-latest
-            python-version: "3.10.11"
-          - os: windows-latest
-            python-version: "3.9.13"
+            python-version: '3.10.11'
 
     runs-on: ${{ matrix.os }}
     steps:
@@ -119,6 +114,26 @@ jobs:
           --benchmark-json "./reflex-examples/counter/${{ env.OUTPUT_FILE }}"
           --branch-name "${{ github.head_ref || github.ref_name }}" --pr-id "${{ github.event.pull_request.id }}"
           --app-name "counter"
+      - name: Install requirements for nba proxy example
+        working-directory: ./reflex-examples/nba-proxy
+        run: |
+          poetry run uv pip install -r requirements.txt
+      - name: Install additional dependencies for DB access
+        run: poetry run uv pip install psycopg
+      - name: Check export --backend-only before init for nba-proxy example
+        working-directory: ./reflex-examples/nba-proxy
+        run: |
+          poetry run reflex export --backend-only
+      - name: Init Website for nba-proxy example
+        working-directory: ./reflex-examples/nba-proxy
+        run: |
+          poetry run reflex init --loglevel debug
+      - name: Run Website and Check for errors
+        run: |
+          # Check that npm is home
+          npm -v
+          poetry run bash scripts/integration.sh ./reflex-examples/nba-proxy dev
+
 
   reflex-web:
     strategy:

+ 3 - 7
.github/workflows/unit_tests.yml

@@ -28,22 +28,18 @@ jobs:
       fail-fast: false
       matrix:
         os: [ubuntu-latest, windows-latest]
-        python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
+        python-version: ["3.10.16", "3.11.11", "3.12.8", "3.13.1"]
         # Windows is a bit behind on Python version availability in Github
         exclude:
           - os: windows-latest
             python-version: "3.11.11"
           - os: windows-latest
             python-version: "3.10.16"
-          - os: windows-latest
-            python-version: "3.9.21"
         include:
           - os: windows-latest
             python-version: "3.11.9"
           - os: windows-latest
             python-version: "3.10.11"
-          - os: windows-latest
-            python-version: "3.9.13"
     runs-on: ${{ matrix.os }}
 
     # Service containers to run with `runner-job`
@@ -92,8 +88,8 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        # Note: py39, py310, py311 versions chosen due to available arm64 darwin builds.
-        python-version: ["3.9.13", "3.10.11", "3.11.9", "3.12.8", "3.13.1"]
+        # Note: py310, py311 versions chosen due to available arm64 darwin builds.
+        python-version: ["3.10.11", "3.11.9", "3.12.8", "3.13.1"]
     runs-on: macos-latest
     steps:
       - uses: actions/checkout@v4

+ 1 - 1
.pre-commit-config.yaml

@@ -28,7 +28,7 @@ repos:
         entry: python3 scripts/make_pyi.py
 
   - repo: https://github.com/RobertCraigie/pyright-python
-    rev: v1.1.313
+    rev: v1.1.334
     hooks:
       - id: pyright
         args: [reflex, tests]

+ 2 - 2
CONTRIBUTING.md

@@ -8,7 +8,7 @@ Here is a quick guide on how to run Reflex repo locally so you can start contrib
 
 **Prerequisites:**
 
-- Python >= 3.9
+- Python >= 3.10
 - Poetry version >= 1.4.0 and add it to your path (see [Poetry Docs](https://python-poetry.org/docs/#installation) for more info).
 
 **1. Fork this repository:**
@@ -87,7 +87,7 @@ poetry run ruff format .
 ```
 
 Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and `make_pyi` will run automatically before each commit.
-Note that pre-commit will only be installed when you use a Python version >= 3.9.
+Note that pre-commit will only be installed when you use a Python version >= 3.10.
 
 ``` bash
 pre-commit install

+ 1 - 1
README.md

@@ -34,7 +34,7 @@ See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architectu
 
 ## ⚙️ Installation
 
-Open a terminal and run (Requires Python 3.9+):
+Open a terminal and run (Requires Python 3.10+):
 
 ```bash
 pip install reflex

+ 1 - 1
docs/de/README.md

@@ -34,7 +34,7 @@ Auf unserer [Architektur-Seite](https://reflex.dev/blog/2024-03-21-reflex-archit
 
 ## ⚙️ Installation
 
-Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.9+):
+Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.10+):
 
 ```bash
 pip install reflex

+ 1 - 1
docs/es/README.md

@@ -35,7 +35,7 @@ Consulta nuestra [página de arquitectura](https://reflex.dev/blog/2024-03-21-re
 
 ## ⚙️ Instalación
 
-Abra un terminal y ejecute (Requiere Python 3.9+):
+Abra un terminal y ejecute (Requiere Python 3.10+):
 
 ```bash
 pip install reflex

+ 1 - 1
docs/in/README.md

@@ -35,7 +35,7 @@ Reflex के अंदर के कामकाज को जानने क
 
 ## ⚙️ इंस्टॉलेशन (Installation)
 
-एक टर्मिनल खोलें और चलाएं (Python 3.9+ की आवश्यकता है):
+एक टर्मिनल खोलें और चलाएं (Python 3.10+ की आवश्यकता है):
 
 ```bash
 pip install reflex

+ 1 - 1
docs/it/README.md

@@ -22,7 +22,7 @@
 
 ## ⚙️ Installazione
 
-Apri un terminale ed esegui (Richiede Python 3.9+):
+Apri un terminale ed esegui (Richiede Python 3.10+):
 
 ```bash
 pip install reflex

+ 1 - 1
docs/ja/README.md

@@ -37,7 +37,7 @@ Reflex がどのように動作しているかを知るには、[アーキテク
 
 ## ⚙️ インストール
 
-ターミナルを開いて以下のコマンドを実行してください。(Python 3.9 以上が必要です。):
+ターミナルを開いて以下のコマンドを実行してください。(Python 3.10 以上が必要です。):
 
 ```bash
 pip install reflex

+ 1 - 1
docs/kr/README.md

@@ -20,7 +20,7 @@
 ---
 ## ⚙️ 설치
 
-터미널을 열고 실행하세요. (Python 3.9+ 필요):
+터미널을 열고 실행하세요. (Python 3.10+ 필요):
 
 ```bash
 pip install reflex

+ 1 - 1
docs/pe/README.md

@@ -34,7 +34,7 @@
 
 ## ⚙️ Installation - نصب و راه اندازی
 
-یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.9+):
+یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.10+):
 
 ```bash
 pip install reflex

+ 1 - 1
docs/pt/pt_br/README.md

@@ -21,7 +21,7 @@
 ---
 ## ⚙️ Instalação
 
-Abra um terminal e execute (Requer Python 3.9+):
+Abra um terminal e execute (Requer Python 3.10+):
 
 ```bash
 pip install reflex

+ 1 - 1
docs/tr/README.md

@@ -24,7 +24,7 @@
 
 ## ⚙️ Kurulum
 
-Bir terminal açın ve çalıştırın (Python 3.9+ gerekir):
+Bir terminal açın ve çalıştırın (Python 3.10+ gerekir):
 
 ```bash
 pip install reflex

+ 1 - 1
docs/vi/README.md

@@ -34,7 +34,7 @@ Các tính năng chính:
 
 ## ⚙️ Cài đặt
 
-Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.9+):
+Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.10+):
 
 ```bash
 pip install reflex

+ 1 - 1
docs/zh/zh_cn/README.md

@@ -34,7 +34,7 @@ Reflex 是一个使用纯Python构建全栈web应用的库。
 
 ## ⚙️ 安装
 
-打开一个终端并且运行(要求Python3.9+):
+打开一个终端并且运行(要求Python3.10+):
 
 ```bash
 pip install reflex

+ 1 - 1
docs/zh/zh_tw/README.md

@@ -36,7 +36,7 @@ Reflex 是一個可以用純 Python 構建全端網頁應用程式的函式庫
 
 ## ⚙️ 安裝
 
-開啟一個終端機並且執行 (需要 Python 3.9+):
+開啟一個終端機並且執行 (需要 Python 3.10+):
 
 ```bash
 pip install reflex

+ 218 - 281
poetry.lock

@@ -2,13 +2,13 @@
 
 [[package]]
 name = "alembic"
-version = "1.14.0"
+version = "1.14.1"
 description = "A database migration tool for SQLAlchemy."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "alembic-1.14.0-py3-none-any.whl", hash = "sha256:99bd884ca390466db5e27ffccff1d179ec5c05c965cfefc0607e69f9e411cb25"},
-    {file = "alembic-1.14.0.tar.gz", hash = "sha256:b00892b53b3642d0b8dbedba234dbf1924b69be83a9a769d5a624b01094e304b"},
+    {file = "alembic-1.14.1-py3-none-any.whl", hash = "sha256:1acdd7a3a478e208b0503cd73614d5e4c6efafa4e73518bb60e4f2846a37b1c5"},
+    {file = "alembic-1.14.1.tar.gz", hash = "sha256:496e888245a53adf1498fcab31713a469c65836f8de76e01399aa1c3e90dd213"},
 ]
 
 [package.dependencies]
@@ -17,7 +17,7 @@ SQLAlchemy = ">=1.3.0"
 typing-extensions = ">=4"
 
 [package.extras]
-tz = ["backports.zoneinfo"]
+tz = ["backports.zoneinfo", "tzdata"]
 
 [[package]]
 name = "annotated-types"
@@ -450,51 +450,53 @@ toml = ["tomli"]
 
 [[package]]
 name = "cryptography"
-version = "43.0.3"
+version = "44.0.0"
 description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
 optional = false
-python-versions = ">=3.7"
-files = [
-    {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"},
-    {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"},
-    {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"},
-    {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"},
-    {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"},
-    {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"},
-    {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"},
-    {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"},
-    {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"},
-    {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"},
-    {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"},
-    {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"},
-    {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"},
-    {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"},
-    {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"},
-    {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"},
-    {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"},
-    {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"},
-    {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"},
-    {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"},
-    {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"},
-    {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"},
-    {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"},
-    {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"},
-    {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"},
-    {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"},
-    {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"},
+python-versions = "!=3.9.0,!=3.9.1,>=3.7"
+files = [
+    {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"},
+    {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"},
+    {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"},
+    {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"},
+    {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"},
+    {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"},
+    {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"},
+    {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"},
+    {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"},
+    {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"},
+    {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"},
+    {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"},
+    {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"},
+    {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"},
+    {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"},
+    {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"},
+    {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"},
+    {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"},
+    {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"},
+    {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"},
+    {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"},
+    {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"},
+    {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"},
+    {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"},
+    {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"},
+    {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"},
+    {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"},
+    {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"},
+    {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"},
 ]
 
 [package.dependencies]
 cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
 
 [package.extras]
-docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
-docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
-nox = ["nox"]
-pep8test = ["check-sdist", "click", "mypy", "ruff"]
-sdist = ["build"]
+docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"]
+docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
+nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"]
+pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
+sdist = ["build (>=1.0.0)"]
 ssh = ["bcrypt (>=3.1.5)"]
-test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
+test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
 test-randomorder = ["pytest-randomly"]
 
 [[package]]
@@ -592,18 +594,18 @@ standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "htt
 
 [[package]]
 name = "filelock"
-version = "3.16.1"
+version = "3.17.0"
 description = "A platform independent file lock."
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 files = [
-    {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
-    {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
+    {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"},
+    {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"},
 ]
 
 [package.extras]
-docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
+docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"]
 typing = ["typing-extensions (>=4.12.2)"]
 
 [[package]]
@@ -769,15 +771,34 @@ http2 = ["h2 (>=3,<5)"]
 socks = ["socksio (==1.*)"]
 zstd = ["zstandard (>=0.18.0)"]
 
+[[package]]
+name = "id"
+version = "1.5.0"
+description = "A tool for generating OIDC identities"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "id-1.5.0-py3-none-any.whl", hash = "sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658"},
+    {file = "id-1.5.0.tar.gz", hash = "sha256:292cb8a49eacbbdbce97244f47a97b4c62540169c976552e497fd57df0734c1d"},
+]
+
+[package.dependencies]
+requests = "*"
+
+[package.extras]
+dev = ["build", "bump (>=1.3.2)", "id[lint,test]"]
+lint = ["bandit", "interrogate", "mypy", "ruff (<0.8.2)", "types-requests"]
+test = ["coverage[toml]", "pretend", "pytest", "pytest-cov"]
+
 [[package]]
 name = "identify"
-version = "2.6.5"
+version = "2.6.6"
 description = "File identification library for Python"
 optional = false
 python-versions = ">=3.9"
 files = [
-    {file = "identify-2.6.5-py2.py3-none-any.whl", hash = "sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566"},
-    {file = "identify-2.6.5.tar.gz", hash = "sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc"},
+    {file = "identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881"},
+    {file = "identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251"},
 ]
 
 [package.extras]
@@ -799,13 +820,13 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2
 
 [[package]]
 name = "importlib-metadata"
-version = "8.5.0"
+version = "8.6.1"
 description = "Read metadata from Python packages"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 files = [
-    {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"},
-    {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"},
+    {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"},
+    {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"},
 ]
 
 [package.dependencies]
@@ -817,7 +838,7 @@ cover = ["pytest-cov"]
 doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
 enabler = ["pytest-enabler (>=2.2)"]
 perf = ["ipython"]
-test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
+test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
 type = ["pytest-mypy"]
 
 [[package]]
@@ -1150,120 +1171,66 @@ files = [
 
 [[package]]
 name = "numpy"
-version = "2.0.2"
-description = "Fundamental package for array computing in Python"
-optional = false
-python-versions = ">=3.9"
-files = [
-    {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"},
-    {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"},
-    {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"},
-    {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"},
-    {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"},
-    {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"},
-    {file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"},
-    {file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"},
-    {file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"},
-    {file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"},
-    {file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"},
-    {file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"},
-    {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"},
-    {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"},
-    {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"},
-    {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"},
-    {file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"},
-    {file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"},
-    {file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"},
-    {file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"},
-    {file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"},
-    {file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"},
-    {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"},
-    {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"},
-    {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"},
-    {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"},
-    {file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"},
-    {file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"},
-    {file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"},
-    {file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"},
-    {file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"},
-    {file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"},
-    {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"},
-    {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"},
-    {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"},
-    {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"},
-    {file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"},
-    {file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"},
-    {file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"},
-    {file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"},
-    {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"},
-    {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"},
-    {file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"},
-    {file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"},
-    {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"},
-]
-
-[[package]]
-name = "numpy"
-version = "2.2.1"
+version = "2.2.2"
 description = "Fundamental package for array computing in Python"
 optional = false
 python-versions = ">=3.10"
 files = [
-    {file = "numpy-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5edb4e4caf751c1518e6a26a83501fda79bff41cc59dac48d70e6d65d4ec4440"},
-    {file = "numpy-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa3017c40d513ccac9621a2364f939d39e550c542eb2a894b4c8da92b38896ab"},
-    {file = "numpy-2.2.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:61048b4a49b1c93fe13426e04e04fdf5a03f456616f6e98c7576144677598675"},
-    {file = "numpy-2.2.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7671dc19c7019103ca44e8d94917eba8534c76133523ca8406822efdd19c9308"},
-    {file = "numpy-2.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4250888bcb96617e00bfa28ac24850a83c9f3a16db471eca2ee1f1714df0f957"},
-    {file = "numpy-2.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7746f235c47abc72b102d3bce9977714c2444bdfaea7888d241b4c4bb6a78bf"},
-    {file = "numpy-2.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:059e6a747ae84fce488c3ee397cee7e5f905fd1bda5fb18c66bc41807ff119b2"},
-    {file = "numpy-2.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f62aa6ee4eb43b024b0e5a01cf65a0bb078ef8c395e8713c6e8a12a697144528"},
-    {file = "numpy-2.2.1-cp310-cp310-win32.whl", hash = "sha256:48fd472630715e1c1c89bf1feab55c29098cb403cc184b4859f9c86d4fcb6a95"},
-    {file = "numpy-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:b541032178a718c165a49638d28272b771053f628382d5e9d1c93df23ff58dbf"},
-    {file = "numpy-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40f9e544c1c56ba8f1cf7686a8c9b5bb249e665d40d626a23899ba6d5d9e1484"},
-    {file = "numpy-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9b57eaa3b0cd8db52049ed0330747b0364e899e8a606a624813452b8203d5f7"},
-    {file = "numpy-2.2.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bc8a37ad5b22c08e2dbd27df2b3ef7e5c0864235805b1e718a235bcb200cf1cb"},
-    {file = "numpy-2.2.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9036d6365d13b6cbe8f27a0eaf73ddcc070cae584e5ff94bb45e3e9d729feab5"},
-    {file = "numpy-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51faf345324db860b515d3f364eaa93d0e0551a88d6218a7d61286554d190d73"},
-    {file = "numpy-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38efc1e56b73cc9b182fe55e56e63b044dd26a72128fd2fbd502f75555d92591"},
-    {file = "numpy-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:31b89fa67a8042e96715c68e071a1200c4e172f93b0fbe01a14c0ff3ff820fc8"},
-    {file = "numpy-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c86e2a209199ead7ee0af65e1d9992d1dce7e1f63c4b9a616500f93820658d0"},
-    {file = "numpy-2.2.1-cp311-cp311-win32.whl", hash = "sha256:b34d87e8a3090ea626003f87f9392b3929a7bbf4104a05b6667348b6bd4bf1cd"},
-    {file = "numpy-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:360137f8fb1b753c5cde3ac388597ad680eccbbbb3865ab65efea062c4a1fd16"},
-    {file = "numpy-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:694f9e921a0c8f252980e85bce61ebbd07ed2b7d4fa72d0e4246f2f8aa6642ab"},
-    {file = "numpy-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3683a8d166f2692664262fd4900f207791d005fb088d7fdb973cc8d663626faa"},
-    {file = "numpy-2.2.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:780077d95eafc2ccc3ced969db22377b3864e5b9a0ea5eb347cc93b3ea900315"},
-    {file = "numpy-2.2.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:55ba24ebe208344aa7a00e4482f65742969a039c2acfcb910bc6fcd776eb4355"},
-    {file = "numpy-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b1d07b53b78bf84a96898c1bc139ad7f10fda7423f5fd158fd0f47ec5e01ac7"},
-    {file = "numpy-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5062dc1a4e32a10dc2b8b13cedd58988261416e811c1dc4dbdea4f57eea61b0d"},
-    {file = "numpy-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fce4f615f8ca31b2e61aa0eb5865a21e14f5629515c9151850aa936c02a1ee51"},
-    {file = "numpy-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67d4cda6fa6ffa073b08c8372aa5fa767ceb10c9a0587c707505a6d426f4e046"},
-    {file = "numpy-2.2.1-cp312-cp312-win32.whl", hash = "sha256:32cb94448be47c500d2c7a95f93e2f21a01f1fd05dd2beea1ccd049bb6001cd2"},
-    {file = "numpy-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:ba5511d8f31c033a5fcbda22dd5c813630af98c70b2661f2d2c654ae3cdfcfc8"},
-    {file = "numpy-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f1d09e520217618e76396377c81fba6f290d5f926f50c35f3a5f72b01a0da780"},
-    {file = "numpy-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ecc47cd7f6ea0336042be87d9e7da378e5c7e9b3c8ad0f7c966f714fc10d821"},
-    {file = "numpy-2.2.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f419290bc8968a46c4933158c91a0012b7a99bb2e465d5ef5293879742f8797e"},
-    {file = "numpy-2.2.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5b6c390bfaef8c45a260554888966618328d30e72173697e5cabe6b285fb2348"},
-    {file = "numpy-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:526fc406ab991a340744aad7e25251dd47a6720a685fa3331e5c59fef5282a59"},
-    {file = "numpy-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74e6fdeb9a265624ec3a3918430205dff1df7e95a230779746a6af78bc615af"},
-    {file = "numpy-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:53c09385ff0b72ba79d8715683c1168c12e0b6e84fb0372e97553d1ea91efe51"},
-    {file = "numpy-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3eac17d9ec51be534685ba877b6ab5edc3ab7ec95c8f163e5d7b39859524716"},
-    {file = "numpy-2.2.1-cp313-cp313-win32.whl", hash = "sha256:9ad014faa93dbb52c80d8f4d3dcf855865c876c9660cb9bd7553843dd03a4b1e"},
-    {file = "numpy-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:164a829b6aacf79ca47ba4814b130c4020b202522a93d7bff2202bfb33b61c60"},
-    {file = "numpy-2.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4dfda918a13cc4f81e9118dea249e192ab167a0bb1966272d5503e39234d694e"},
-    {file = "numpy-2.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:733585f9f4b62e9b3528dd1070ec4f52b8acf64215b60a845fa13ebd73cd0712"},
-    {file = "numpy-2.2.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:89b16a18e7bba224ce5114db863e7029803c179979e1af6ad6a6b11f70545008"},
-    {file = "numpy-2.2.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:676f4eebf6b2d430300f1f4f4c2461685f8269f94c89698d832cdf9277f30b84"},
-    {file = "numpy-2.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f5cdf9f493b35f7e41e8368e7d7b4bbafaf9660cba53fb21d2cd174ec09631"},
-    {file = "numpy-2.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1ad395cf254c4fbb5b2132fee391f361a6e8c1adbd28f2cd8e79308a615fe9d"},
-    {file = "numpy-2.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:08ef779aed40dbc52729d6ffe7dd51df85796a702afbf68a4f4e41fafdc8bda5"},
-    {file = "numpy-2.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:26c9c4382b19fcfbbed3238a14abf7ff223890ea1936b8890f058e7ba35e8d71"},
-    {file = "numpy-2.2.1-cp313-cp313t-win32.whl", hash = "sha256:93cf4e045bae74c90ca833cba583c14b62cb4ba2cba0abd2b141ab52548247e2"},
-    {file = "numpy-2.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bff7d8ec20f5f42607599f9994770fa65d76edca264a87b5e4ea5629bce12268"},
-    {file = "numpy-2.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7ba9cc93a91d86365a5d270dee221fdc04fb68d7478e6bf6af650de78a8339e3"},
-    {file = "numpy-2.2.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3d03883435a19794e41f147612a77a8f56d4e52822337844fff3d4040a142964"},
-    {file = "numpy-2.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4511d9e6071452b944207c8ce46ad2f897307910b402ea5fa975da32e0102800"},
-    {file = "numpy-2.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5c5cc0cbabe9452038ed984d05ac87910f89370b9242371bd9079cb4af61811e"},
-    {file = "numpy-2.2.1.tar.gz", hash = "sha256:45681fd7128c8ad1c379f0ca0776a8b0c6583d2f69889ddac01559dfe4390918"},
+    {file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"},
+    {file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"},
+    {file = "numpy-2.2.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715"},
+    {file = "numpy-2.2.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a"},
+    {file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97"},
+    {file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957"},
+    {file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d"},
+    {file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd"},
+    {file = "numpy-2.2.2-cp310-cp310-win32.whl", hash = "sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160"},
+    {file = "numpy-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014"},
+    {file = "numpy-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189"},
+    {file = "numpy-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323"},
+    {file = "numpy-2.2.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac"},
+    {file = "numpy-2.2.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e"},
+    {file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c"},
+    {file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f"},
+    {file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826"},
+    {file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8"},
+    {file = "numpy-2.2.2-cp311-cp311-win32.whl", hash = "sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50"},
+    {file = "numpy-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2"},
+    {file = "numpy-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467"},
+    {file = "numpy-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a"},
+    {file = "numpy-2.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825"},
+    {file = "numpy-2.2.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37"},
+    {file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748"},
+    {file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0"},
+    {file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278"},
+    {file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba"},
+    {file = "numpy-2.2.2-cp312-cp312-win32.whl", hash = "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283"},
+    {file = "numpy-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb"},
+    {file = "numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc"},
+    {file = "numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369"},
+    {file = "numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd"},
+    {file = "numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be"},
+    {file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84"},
+    {file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff"},
+    {file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0"},
+    {file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de"},
+    {file = "numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9"},
+    {file = "numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369"},
+    {file = "numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391"},
+    {file = "numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39"},
+    {file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317"},
+    {file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49"},
+    {file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2"},
+    {file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7"},
+    {file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb"},
+    {file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648"},
+    {file = "numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4"},
+    {file = "numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576"},
+    {file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495"},
+    {file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df"},
+    {file = "numpy-2.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a"},
+    {file = "numpy-2.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60"},
+    {file = "numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f"},
 ]
 
 [[package]]
@@ -1494,20 +1461,6 @@ pip = ">=23.1.2"
 graphviz = ["graphviz (>=0.20.1)"]
 test = ["covdefaults (>=2.3)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "virtualenv (>=20.25,<21)"]
 
-[[package]]
-name = "pkginfo"
-version = "1.12.0"
-description = "Query metadata from sdists / bdists / installed packages."
-optional = false
-python-versions = ">=3.8"
-files = [
-    {file = "pkginfo-1.12.0-py3-none-any.whl", hash = "sha256:dcd589c9be4da8973eceffa247733c144812759aa67eaf4bbf97016a02f39088"},
-    {file = "pkginfo-1.12.0.tar.gz", hash = "sha256:8ad91a0445a036782b9366ef8b8c2c50291f83a553478ba8580c73d3215700cf"},
-]
-
-[package.extras]
-testing = ["pytest", "pytest-cov", "wheel"]
-
 [[package]]
 name = "platformdirs"
 version = "4.3.6"
@@ -1576,13 +1529,13 @@ testing = ["pytest", "pytest-benchmark"]
 
 [[package]]
 name = "pre-commit"
-version = "4.0.1"
+version = "4.1.0"
 description = "A framework for managing and maintaining multi-language pre-commit hooks."
 optional = false
 python-versions = ">=3.9"
 files = [
-    {file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"},
-    {file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"},
+    {file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"},
+    {file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"},
 ]
 
 [package.dependencies]
@@ -2182,20 +2135,6 @@ async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\
 hiredis = ["hiredis (>=3.0.0)"]
 ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"]
 
-[[package]]
-name = "reflex-chakra"
-version = "0.6.2"
-description = "reflex using chakra components"
-optional = false
-python-versions = "<4.0,>=3.9"
-files = [
-    {file = "reflex_chakra-0.6.2-py3-none-any.whl", hash = "sha256:b8aa19f39a02601c560b97f4b17f171c0b5980e13a58069e3a5dd0999e362e4f"},
-    {file = "reflex_chakra-0.6.2.tar.gz", hash = "sha256:81ddb7f182cc454922cc817312755b799d4e1a49a46ef2e81305052dc76ef86d"},
-]
-
-[package.dependencies]
-reflex = ">=0.6.0a"
-
 [[package]]
 name = "reflex-hosting-cli"
 version = "0.1.32"
@@ -2332,13 +2271,13 @@ jeepney = ">=0.6"
 
 [[package]]
 name = "selenium"
-version = "4.27.1"
+version = "4.28.0"
 description = "Official Python bindings for Selenium WebDriver"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 files = [
-    {file = "selenium-4.27.1-py3-none-any.whl", hash = "sha256:b89b1f62b5cfe8025868556fe82360d6b649d464f75d2655cb966c8f8447ea18"},
-    {file = "selenium-4.27.1.tar.gz", hash = "sha256:5296c425a75ff1b44d0d5199042b36a6d1ef76c04fb775b97b40be739a9caae2"},
+    {file = "selenium-4.28.0-py3-none-any.whl", hash = "sha256:3d6a2e8e1b850a1078884ea19f4e011ecdc12263434d87a0b78769836fb82dd8"},
+    {file = "selenium-4.28.0.tar.gz", hash = "sha256:a9fae6eef48d470a1b0c6e45185d96f0dafb025e8da4b346cc41e4da3ac54fa0"},
 ]
 
 [package.dependencies]
@@ -2540,7 +2479,6 @@ files = [
 
 [package.dependencies]
 anyio = ">=3.4.0,<5"
-typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
 
 [package.extras]
 full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"]
@@ -2709,20 +2647,19 @@ wsproto = ">=0.14"
 
 [[package]]
 name = "twine"
-version = "6.0.1"
+version = "6.1.0"
 description = "Collection of utilities for publishing packages on PyPI"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "twine-6.0.1-py3-none-any.whl", hash = "sha256:9c6025b203b51521d53e200f4a08b116dee7500a38591668c6a6033117bdc218"},
-    {file = "twine-6.0.1.tar.gz", hash = "sha256:36158b09df5406e1c9c1fb8edb24fc2be387709443e7376689b938531582ee27"},
+    {file = "twine-6.1.0-py3-none-any.whl", hash = "sha256:a47f973caf122930bf0fbbf17f80b83bc1602c9ce393c7845f289a3001dc5384"},
+    {file = "twine-6.1.0.tar.gz", hash = "sha256:be324f6272eff91d07ee93f251edf232fc647935dd585ac003539b42404a8dbd"},
 ]
 
 [package.dependencies]
-importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""}
+id = "*"
 keyring = {version = ">=15.1", markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\""}
-packaging = "*"
-pkginfo = ">=1.8.1"
+packaging = ">=24.0"
 readme-renderer = ">=35.0"
 requests = ">=2.20"
 requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0"
@@ -2763,13 +2700,13 @@ files = [
 
 [[package]]
 name = "tzdata"
-version = "2024.2"
+version = "2025.1"
 description = "Provider of IANA time zone data"
 optional = false
 python-versions = ">=2"
 files = [
-    {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"},
-    {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"},
+    {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"},
+    {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"},
 ]
 
 [[package]]
@@ -2849,80 +2786,80 @@ test = ["websockets"]
 
 [[package]]
 name = "websockets"
-version = "14.1"
+version = "14.2"
 description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
 optional = false
 python-versions = ">=3.9"
 files = [
-    {file = "websockets-14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a0adf84bc2e7c86e8a202537b4fd50e6f7f0e4a6b6bf64d7ccb96c4cd3330b29"},
-    {file = "websockets-14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90b5d9dfbb6d07a84ed3e696012610b6da074d97453bd01e0e30744b472c8179"},
-    {file = "websockets-14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2177ee3901075167f01c5e335a6685e71b162a54a89a56001f1c3e9e3d2ad250"},
-    {file = "websockets-14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f14a96a0034a27f9d47fd9788913924c89612225878f8078bb9d55f859272b0"},
-    {file = "websockets-14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f874ba705deea77bcf64a9da42c1f5fc2466d8f14daf410bc7d4ceae0a9fcb0"},
-    {file = "websockets-14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199"},
-    {file = "websockets-14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bea45f19b7ca000380fbd4e02552be86343080120d074b87f25593ce1700ad58"},
-    {file = "websockets-14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:219c8187b3ceeadbf2afcf0f25a4918d02da7b944d703b97d12fb01510869078"},
-    {file = "websockets-14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad2ab2547761d79926effe63de21479dfaf29834c50f98c4bf5b5480b5838434"},
-    {file = "websockets-14.1-cp310-cp310-win32.whl", hash = "sha256:1288369a6a84e81b90da5dbed48610cd7e5d60af62df9851ed1d1d23a9069f10"},
-    {file = "websockets-14.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0744623852f1497d825a49a99bfbec9bea4f3f946df6eb9d8a2f0c37a2fec2e"},
-    {file = "websockets-14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:449d77d636f8d9c17952628cc7e3b8faf6e92a17ec581ec0c0256300717e1512"},
-    {file = "websockets-14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a35f704be14768cea9790d921c2c1cc4fc52700410b1c10948511039be824aac"},
-    {file = "websockets-14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b1f3628a0510bd58968c0f60447e7a692933589b791a6b572fcef374053ca280"},
-    {file = "websockets-14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c3deac3748ec73ef24fc7be0b68220d14d47d6647d2f85b2771cb35ea847aa1"},
-    {file = "websockets-14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7048eb4415d46368ef29d32133134c513f507fff7d953c18c91104738a68c3b3"},
-    {file = "websockets-14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cf0ad281c979306a6a34242b371e90e891bce504509fb6bb5246bbbf31e7b6"},
-    {file = "websockets-14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc1fc87428c1d18b643479caa7b15db7d544652e5bf610513d4a3478dbe823d0"},
-    {file = "websockets-14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f95ba34d71e2fa0c5d225bde3b3bdb152e957150100e75c86bc7f3964c450d89"},
-    {file = "websockets-14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9481a6de29105d73cf4515f2bef8eb71e17ac184c19d0b9918a3701c6c9c4f23"},
-    {file = "websockets-14.1-cp311-cp311-win32.whl", hash = "sha256:368a05465f49c5949e27afd6fbe0a77ce53082185bbb2ac096a3a8afaf4de52e"},
-    {file = "websockets-14.1-cp311-cp311-win_amd64.whl", hash = "sha256:6d24fc337fc055c9e83414c94e1ee0dee902a486d19d2a7f0929e49d7d604b09"},
-    {file = "websockets-14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed907449fe5e021933e46a3e65d651f641975a768d0649fee59f10c2985529ed"},
-    {file = "websockets-14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87e31011b5c14a33b29f17eb48932e63e1dcd3fa31d72209848652310d3d1f0d"},
-    {file = "websockets-14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc6ccf7d54c02ae47a48ddf9414c54d48af9c01076a2e1023e3b486b6e72c707"},
-    {file = "websockets-14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9777564c0a72a1d457f0848977a1cbe15cfa75fa2f67ce267441e465717dcf1a"},
-    {file = "websockets-14.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a655bde548ca98f55b43711b0ceefd2a88a71af6350b0c168aa77562104f3f45"},
-    {file = "websockets-14.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfff83ca578cada2d19e665e9c8368e1598d4e787422a460ec70e531dbdd58"},
-    {file = "websockets-14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6a6c9bcf7cdc0fd41cc7b7944447982e8acfd9f0d560ea6d6845428ed0562058"},
-    {file = "websockets-14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4b6caec8576e760f2c7dd878ba817653144d5f369200b6ddf9771d64385b84d4"},
-    {file = "websockets-14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb6d38971c800ff02e4a6afd791bbe3b923a9a57ca9aeab7314c21c84bf9ff05"},
-    {file = "websockets-14.1-cp312-cp312-win32.whl", hash = "sha256:1d045cbe1358d76b24d5e20e7b1878efe578d9897a25c24e6006eef788c0fdf0"},
-    {file = "websockets-14.1-cp312-cp312-win_amd64.whl", hash = "sha256:90f4c7a069c733d95c308380aae314f2cb45bd8a904fb03eb36d1a4983a4993f"},
-    {file = "websockets-14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3630b670d5057cd9e08b9c4dab6493670e8e762a24c2c94ef312783870736ab9"},
-    {file = "websockets-14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36ebd71db3b89e1f7b1a5deaa341a654852c3518ea7a8ddfdf69cc66acc2db1b"},
-    {file = "websockets-14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5b918d288958dc3fa1c5a0b9aa3256cb2b2b84c54407f4813c45d52267600cd3"},
-    {file = "websockets-14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00fe5da3f037041da1ee0cf8e308374e236883f9842c7c465aa65098b1c9af59"},
-    {file = "websockets-14.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8149a0f5a72ca36720981418eeffeb5c2729ea55fa179091c81a0910a114a5d2"},
-    {file = "websockets-14.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77569d19a13015e840b81550922056acabc25e3f52782625bc6843cfa034e1da"},
-    {file = "websockets-14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf5201a04550136ef870aa60ad3d29d2a59e452a7f96b94193bee6d73b8ad9a9"},
-    {file = "websockets-14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:88cf9163ef674b5be5736a584c999e98daf3aabac6e536e43286eb74c126b9c7"},
-    {file = "websockets-14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:836bef7ae338a072e9d1863502026f01b14027250a4545672673057997d5c05a"},
-    {file = "websockets-14.1-cp313-cp313-win32.whl", hash = "sha256:0d4290d559d68288da9f444089fd82490c8d2744309113fc26e2da6e48b65da6"},
-    {file = "websockets-14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8621a07991add373c3c5c2cf89e1d277e49dc82ed72c75e3afc74bd0acc446f0"},
-    {file = "websockets-14.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01bb2d4f0a6d04538d3c5dfd27c0643269656c28045a53439cbf1c004f90897a"},
-    {file = "websockets-14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:414ffe86f4d6f434a8c3b7913655a1a5383b617f9bf38720e7c0799fac3ab1c6"},
-    {file = "websockets-14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fda642151d5affdee8a430bd85496f2e2517be3a2b9d2484d633d5712b15c56"},
-    {file = "websockets-14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd7c11968bc3860d5c78577f0dbc535257ccec41750675d58d8dc66aa47fe52c"},
-    {file = "websockets-14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a032855dc7db987dff813583d04f4950d14326665d7e714d584560b140ae6b8b"},
-    {file = "websockets-14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7e7ea2f782408c32d86b87a0d2c1fd8871b0399dd762364c731d86c86069a78"},
-    {file = "websockets-14.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:39450e6215f7d9f6f7bc2a6da21d79374729f5d052333da4d5825af8a97e6735"},
-    {file = "websockets-14.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ceada5be22fa5a5a4cdeec74e761c2ee7db287208f54c718f2df4b7e200b8d4a"},
-    {file = "websockets-14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3fc753451d471cff90b8f467a1fc0ae64031cf2d81b7b34e1811b7e2691bc4bc"},
-    {file = "websockets-14.1-cp39-cp39-win32.whl", hash = "sha256:14839f54786987ccd9d03ed7f334baec0f02272e7ec4f6e9d427ff584aeea8b4"},
-    {file = "websockets-14.1-cp39-cp39-win_amd64.whl", hash = "sha256:d9fd19ecc3a4d5ae82ddbfb30962cf6d874ff943e56e0c81f5169be2fda62979"},
-    {file = "websockets-14.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5dc25a9dbd1a7f61eca4b7cb04e74ae4b963d658f9e4f9aad9cd00b688692c8"},
-    {file = "websockets-14.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:04a97aca96ca2acedf0d1f332c861c5a4486fdcba7bcef35873820f940c4231e"},
-    {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df174ece723b228d3e8734a6f2a6febbd413ddec39b3dc592f5a4aa0aff28098"},
-    {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:034feb9f4286476f273b9a245fb15f02c34d9586a5bc936aff108c3ba1b21beb"},
-    {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c308dabd2b380807ab64b62985eaccf923a78ebc572bd485375b9ca2b7dc7"},
-    {file = "websockets-14.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a42d3ecbb2db5080fc578314439b1d79eef71d323dc661aa616fb492436af5d"},
-    {file = "websockets-14.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddaa4a390af911da6f680be8be4ff5aaf31c4c834c1a9147bc21cbcbca2d4370"},
-    {file = "websockets-14.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a4c805c6034206143fbabd2d259ec5e757f8b29d0a2f0bf3d2fe5d1f60147a4a"},
-    {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:205f672a6c2c671a86d33f6d47c9b35781a998728d2c7c2a3e1cf3333fcb62b7"},
-    {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef440054124728cc49b01c33469de06755e5a7a4e83ef61934ad95fc327fbb0"},
-    {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7591d6f440af7f73c4bd9404f3772bfee064e639d2b6cc8c94076e71b2471c1"},
-    {file = "websockets-14.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:25225cc79cfebc95ba1d24cd3ab86aaa35bcd315d12fa4358939bd55e9bd74a5"},
-    {file = "websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e"},
-    {file = "websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8"},
+    {file = "websockets-14.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e8179f95323b9ab1c11723e5d91a89403903f7b001828161b480a7810b334885"},
+    {file = "websockets-14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d8c3e2cdb38f31d8bd7d9d28908005f6fa9def3324edb9bf336d7e4266fd397"},
+    {file = "websockets-14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:714a9b682deb4339d39ffa674f7b674230227d981a37d5d174a4a83e3978a610"},
+    {file = "websockets-14.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2e53c72052f2596fb792a7acd9704cbc549bf70fcde8a99e899311455974ca3"},
+    {file = "websockets-14.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3fbd68850c837e57373d95c8fe352203a512b6e49eaae4c2f4088ef8cf21980"},
+    {file = "websockets-14.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b27ece32f63150c268593d5fdb82819584831a83a3f5809b7521df0685cd5d8"},
+    {file = "websockets-14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4daa0faea5424d8713142b33825fff03c736f781690d90652d2c8b053345b0e7"},
+    {file = "websockets-14.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bc63cee8596a6ec84d9753fd0fcfa0452ee12f317afe4beae6b157f0070c6c7f"},
+    {file = "websockets-14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a570862c325af2111343cc9b0257b7119b904823c675b22d4ac547163088d0d"},
+    {file = "websockets-14.2-cp310-cp310-win32.whl", hash = "sha256:75862126b3d2d505e895893e3deac0a9339ce750bd27b4ba515f008b5acf832d"},
+    {file = "websockets-14.2-cp310-cp310-win_amd64.whl", hash = "sha256:cc45afb9c9b2dc0852d5c8b5321759cf825f82a31bfaf506b65bf4668c96f8b2"},
+    {file = "websockets-14.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bdc8c692c866ce5fefcaf07d2b55c91d6922ac397e031ef9b774e5b9ea42166"},
+    {file = "websockets-14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c93215fac5dadc63e51bcc6dceca72e72267c11def401d6668622b47675b097f"},
+    {file = "websockets-14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c9b6535c0e2cf8a6bf938064fb754aaceb1e6a4a51a80d884cd5db569886910"},
+    {file = "websockets-14.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a52a6d7cf6938e04e9dceb949d35fbdf58ac14deea26e685ab6368e73744e4c"},
+    {file = "websockets-14.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f05702e93203a6ff5226e21d9b40c037761b2cfb637187c9802c10f58e40473"},
+    {file = "websockets-14.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22441c81a6748a53bfcb98951d58d1af0661ab47a536af08920d129b4d1c3473"},
+    {file = "websockets-14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd9b868d78b194790e6236d9cbc46d68aba4b75b22497eb4ab64fa640c3af56"},
+    {file = "websockets-14.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a5a20d5843886d34ff8c57424cc65a1deda4375729cbca4cb6b3353f3ce4142"},
+    {file = "websockets-14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34277a29f5303d54ec6468fb525d99c99938607bc96b8d72d675dee2b9f5bf1d"},
+    {file = "websockets-14.2-cp311-cp311-win32.whl", hash = "sha256:02687db35dbc7d25fd541a602b5f8e451a238ffa033030b172ff86a93cb5dc2a"},
+    {file = "websockets-14.2-cp311-cp311-win_amd64.whl", hash = "sha256:862e9967b46c07d4dcd2532e9e8e3c2825e004ffbf91a5ef9dde519ee2effb0b"},
+    {file = "websockets-14.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f20522e624d7ffbdbe259c6b6a65d73c895045f76a93719aa10cd93b3de100c"},
+    {file = "websockets-14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:647b573f7d3ada919fd60e64d533409a79dcf1ea21daeb4542d1d996519ca967"},
+    {file = "websockets-14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6af99a38e49f66be5a64b1e890208ad026cda49355661549c507152113049990"},
+    {file = "websockets-14.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:091ab63dfc8cea748cc22c1db2814eadb77ccbf82829bac6b2fbe3401d548eda"},
+    {file = "websockets-14.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b374e8953ad477d17e4851cdc66d83fdc2db88d9e73abf755c94510ebddceb95"},
+    {file = "websockets-14.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a39d7eceeea35db85b85e1169011bb4321c32e673920ae9c1b6e0978590012a3"},
+    {file = "websockets-14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a6f3efd47ffd0d12080594f434faf1cd2549b31e54870b8470b28cc1d3817d9"},
+    {file = "websockets-14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:065ce275e7c4ffb42cb738dd6b20726ac26ac9ad0a2a48e33ca632351a737267"},
+    {file = "websockets-14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e9d0e53530ba7b8b5e389c02282f9d2aa47581514bd6049d3a7cffe1385cf5fe"},
+    {file = "websockets-14.2-cp312-cp312-win32.whl", hash = "sha256:20e6dd0984d7ca3037afcb4494e48c74ffb51e8013cac71cf607fffe11df7205"},
+    {file = "websockets-14.2-cp312-cp312-win_amd64.whl", hash = "sha256:44bba1a956c2c9d268bdcdf234d5e5ff4c9b6dc3e300545cbe99af59dda9dcce"},
+    {file = "websockets-14.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f1372e511c7409a542291bce92d6c83320e02c9cf392223272287ce55bc224e"},
+    {file = "websockets-14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4da98b72009836179bb596a92297b1a61bb5a830c0e483a7d0766d45070a08ad"},
+    {file = "websockets-14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8a86a269759026d2bde227652b87be79f8a734e582debf64c9d302faa1e9f03"},
+    {file = "websockets-14.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86cf1aaeca909bf6815ea714d5c5736c8d6dd3a13770e885aafe062ecbd04f1f"},
+    {file = "websockets-14.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b0f6c3ba3b1240f602ebb3971d45b02cc12bd1845466dd783496b3b05783a5"},
+    {file = "websockets-14.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669c3e101c246aa85bc8534e495952e2ca208bd87994650b90a23d745902db9a"},
+    {file = "websockets-14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eabdb28b972f3729348e632ab08f2a7b616c7e53d5414c12108c29972e655b20"},
+    {file = "websockets-14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2066dc4cbcc19f32c12a5a0e8cc1b7ac734e5b64ac0a325ff8353451c4b15ef2"},
+    {file = "websockets-14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ab95d357cd471df61873dadf66dd05dd4709cae001dd6342edafc8dc6382f307"},
+    {file = "websockets-14.2-cp313-cp313-win32.whl", hash = "sha256:a9e72fb63e5f3feacdcf5b4ff53199ec8c18d66e325c34ee4c551ca748623bbc"},
+    {file = "websockets-14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b439ea828c4ba99bb3176dc8d9b933392a2413c0f6b149fdcba48393f573377f"},
+    {file = "websockets-14.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7cd5706caec1686c5d233bc76243ff64b1c0dc445339bd538f30547e787c11fe"},
+    {file = "websockets-14.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec607328ce95a2f12b595f7ae4c5d71bf502212bddcea528290b35c286932b12"},
+    {file = "websockets-14.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da85651270c6bfb630136423037dd4975199e5d4114cae6d3066641adcc9d1c7"},
+    {file = "websockets-14.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ecadc7ce90accf39903815697917643f5b7cfb73c96702318a096c00aa71f5"},
+    {file = "websockets-14.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1979bee04af6a78608024bad6dfcc0cc930ce819f9e10342a29a05b5320355d0"},
+    {file = "websockets-14.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dddacad58e2614a24938a50b85969d56f88e620e3f897b7d80ac0d8a5800258"},
+    {file = "websockets-14.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:89a71173caaf75fa71a09a5f614f450ba3ec84ad9fca47cb2422a860676716f0"},
+    {file = "websockets-14.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6af6a4b26eea4fc06c6818a6b962a952441e0e39548b44773502761ded8cc1d4"},
+    {file = "websockets-14.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:80c8efa38957f20bba0117b48737993643204645e9ec45512579132508477cfc"},
+    {file = "websockets-14.2-cp39-cp39-win32.whl", hash = "sha256:2e20c5f517e2163d76e2729104abc42639c41cf91f7b1839295be43302713661"},
+    {file = "websockets-14.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4c8cef610e8d7c70dea92e62b6814a8cd24fbd01d7103cc89308d2bfe1659ef"},
+    {file = "websockets-14.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d7d9cafbccba46e768be8a8ad4635fa3eae1ffac4c6e7cb4eb276ba41297ed29"},
+    {file = "websockets-14.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c76193c1c044bd1e9b3316dcc34b174bbf9664598791e6fb606d8d29000e070c"},
+    {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd475a974d5352390baf865309fe37dec6831aafc3014ffac1eea99e84e83fc2"},
+    {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6c0097a41968b2e2b54ed3424739aab0b762ca92af2379f152c1aef0187e1c"},
+    {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7ff794c8b36bc402f2e07c0b2ceb4a2424147ed4785ff03e2a7af03711d60a"},
+    {file = "websockets-14.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dec254fcabc7bd488dab64846f588fc5b6fe0d78f641180030f8ea27b76d72c3"},
+    {file = "websockets-14.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bbe03eb853e17fd5b15448328b4ec7fb2407d45fb0245036d06a3af251f8e48f"},
+    {file = "websockets-14.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a3c4aa3428b904d5404a0ed85f3644d37e2cb25996b7f096d77caeb0e96a3b42"},
+    {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:577a4cebf1ceaf0b65ffc42c54856214165fb8ceeba3935852fc33f6b0c55e7f"},
+    {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad1c1d02357b7665e700eca43a31d52814ad9ad9b89b58118bdabc365454b574"},
+    {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f390024a47d904613577df83ba700bd189eedc09c57af0a904e5c39624621270"},
+    {file = "websockets-14.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3c1426c021c38cf92b453cdf371228d3430acd775edee6bac5a4d577efc72365"},
+    {file = "websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b"},
+    {file = "websockets-14.2.tar.gz", hash = "sha256:5059ed9c54945efb321f097084b4c7e52c246f2c869815876a69d1efc4ad6eb5"},
 ]
 
 [[package]]
@@ -3062,5 +2999,5 @@ type = ["pytest-mypy"]
 
 [metadata]
 lock-version = "2.0"
-python-versions = "^3.9"
-content-hash = "ccd6d6b00fdcf40562854380fafdb18c990b7f6a4f2883b33aaeb0351fcdbc06"
+python-versions = "^3.10"
+content-hash = "353bcb291d6d423963b380f3ee33769106487cacd7f8e34d6c420f2a87a66508"

+ 4 - 5
pyproject.toml

@@ -18,7 +18,7 @@ keywords = ["web", "framework"]
 classifiers = ["Development Status :: 4 - Beta"]
 
 [tool.poetry.dependencies]
-python = "^3.9"
+python = "^3.10"
 fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
 gunicorn = ">=20.1.0,<24.0"
 jinja2 = ">=3.1.2,<4.0"
@@ -50,7 +50,6 @@ httpx = ">=0.25.1,<1.0"
 twine = ">=4.0.0,<7.0"
 tomlkit = ">=0.12.4,<1.0"
 lazy_loader = ">=0.4"
-reflex-chakra = ">=0.6.0"
 typing_extensions = ">=4.6.0"
 
 [tool.poetry.group.dev.dependencies]
@@ -88,8 +87,8 @@ reportIncompatibleVariableOverride = false
 target-version = "py39"
 output-format = "concise"
 lint.isort.split-on-trailing-comma = false
-lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "PERF", "PTH", "RUF", "SIM", "T", "W"]
-lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012"]
+lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "PERF", "PTH", "RUF", "SIM", "T", "TRY", "W"]
+lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012", "TRY0"]
 lint.pydocstyle.convention = "google"
 
 [tool.ruff.lint.per-file-ignores]
@@ -104,5 +103,5 @@ asyncio_default_fixture_loop_scope = "function"
 asyncio_mode = "auto"
 
 [tool.codespell]
-skip = "docs/*,*.html,examples/*, *.pyi, *.lock"
+skip = "docs/*,*.html,examples/*, *.pyi, poetry.lock"
 ignore-words-list = "te, TreeE"

+ 1 - 1
reflex/.templates/jinja/custom_components/pyproject.toml.jinja2

@@ -8,7 +8,7 @@ version = "0.0.1"
 description = "Reflex custom component {{ module_name }}"
 readme = "README.md"
 license = { text = "Apache-2.0" }
-requires-python = ">=3.9"
+requires-python = ">=3.10"
 authors = [{ name = "", email = "YOUREMAIL@domain.com" }]
 keywords = ["reflex","reflex-custom-components"]
 

+ 2 - 0
reflex/.templates/web/utils/state.js

@@ -3,6 +3,7 @@ import axios from "axios";
 import io from "socket.io-client";
 import JSON5 from "json5";
 import env from "$/env.json";
+import reflexEnvironment from "$/reflex.json";
 import Cookies from "universal-cookie";
 import { useEffect, useRef, useState } from "react";
 import Router, { useRouter } from "next/router";
@@ -404,6 +405,7 @@ export const connect = async (
   socket.current = io(endpoint.href, {
     path: endpoint["pathname"],
     transports: transports,
+    protocols: env.TEST_MODE ? undefined : [reflexEnvironment.version],
     autoUnref: false,
   });
   // Ensure undefined fields in events are sent as null instead of removed

+ 0 - 16
reflex/__init__.py

@@ -303,7 +303,6 @@ _MAPPING: dict = {
     "event": [
         "EventChain",
         "EventHandler",
-        "background",
         "call_script",
         "call_function",
         "run_script",
@@ -367,19 +366,4 @@ getattr, __dir__, __all__ = lazy_loader.attach(
 
 
 def __getattr__(name):
-    if name == "chakra":
-        from reflex.utils import console
-
-        console.deprecate(
-            "rx.chakra",
-            reason="and moved to a separate package. "
-            "To continue using Chakra UI components, install the `reflex-chakra` package via `pip install "
-            "reflex-chakra`.",
-            deprecation_version="0.6.0",
-            removal_version="0.7.0",
-            dedupe=True,
-        )
-        import reflex_chakra as rc
-
-        return rc
     return getattr(name)

+ 0 - 1
reflex/__init__.pyi

@@ -156,7 +156,6 @@ from .constants import Env as Env
 from .constants.colors import Color as Color
 from .event import EventChain as EventChain
 from .event import EventHandler as EventHandler
-from .event import background as background
 from .event import call_function as call_function
 from .event import call_script as call_script
 from .event import clear_local_storage as clear_local_storage

+ 42 - 14
reflex/app.py

@@ -463,14 +463,8 @@ class App(MiddlewareMixin, LifespanMixin):
 
         Returns:
             The generated component.
-
-        Raises:
-            exceptions.MatchTypeError: If the return types of match cases in rx.match are different.
         """
-        try:
-            return component if isinstance(component, Component) else component()
-        except exceptions.MatchTypeError:
-            raise
+        return component if isinstance(component, Component) else component()
 
     def add_page(
         self,
@@ -564,11 +558,12 @@ class App(MiddlewareMixin, LifespanMixin):
             meta=meta,
         )
 
-    def _compile_page(self, route: str):
+    def _compile_page(self, route: str, save_page: bool = True):
         """Compile a page.
 
         Args:
             route: The route of the page to compile.
+            save_page: If True, the compiled page is saved to self.pages.
         """
         component, enable_state = compiler.compile_unevaluated_page(
             route, self.unevaluated_pages[route], self.state, self.style, self.theme
@@ -579,7 +574,8 @@ class App(MiddlewareMixin, LifespanMixin):
 
         # Add the page.
         self._check_routes_conflict(route)
-        self.pages[route] = component
+        if save_page:
+            self.pages[route] = component
 
     def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
         """Get the load events for a route.
@@ -879,14 +875,16 @@ class App(MiddlewareMixin, LifespanMixin):
             # If a theme component was provided, wrap the app with it
             app_wrappers[(20, "Theme")] = self.theme
 
+        should_compile = self._should_compile()
+
         for route in self.unevaluated_pages:
             console.debug(f"Evaluating page: {route}")
-            self._compile_page(route)
+            self._compile_page(route, save_page=should_compile)
 
         # Add the optional endpoints (_upload)
         self._add_optional_endpoints()
 
-        if not self._should_compile():
+        if not should_compile:
             return
 
         self._validate_var_dependencies()
@@ -1530,7 +1528,11 @@ class EventNamespace(AsyncNamespace):
             sid: The Socket.IO session id.
             environ: The request information, including HTTP headers.
         """
-        pass
+        subprotocol = environ.get("HTTP_SEC_WEBSOCKET_PROTOCOL", None)
+        if subprotocol and subprotocol != constants.Reflex.VERSION:
+            console.warn(
+                f"Frontend version {subprotocol} for session {sid} does not match the backend version {constants.Reflex.VERSION}."
+            )
 
     def on_disconnect(self, sid):
         """Event for when the websocket disconnects.
@@ -1563,10 +1565,36 @@ class EventNamespace(AsyncNamespace):
         Args:
             sid: The Socket.IO session id.
             data: The event data.
+
+        Raises:
+            EventDeserializationError: If the event data is not a dictionary.
         """
         fields = data
-        # Get the event.
-        event = Event(**{k: v for k, v in fields.items() if k in _EVENT_FIELDS})
+
+        if isinstance(fields, str):
+            console.warn(
+                "Received event data as a string. This generally should not happen and may indicate a bug."
+                f" Event data: {fields}"
+            )
+            try:
+                fields = json.loads(fields)
+            except json.JSONDecodeError as ex:
+                raise exceptions.EventDeserializationError(
+                    f"Failed to deserialize event data: {fields}."
+                ) from ex
+
+        if not isinstance(fields, dict):
+            raise exceptions.EventDeserializationError(
+                f"Event data must be a dictionary, but received {fields} of type {type(fields)}."
+            )
+
+        try:
+            # Get the event.
+            event = Event(**{k: v for k, v in fields.items() if k in _EVENT_FIELDS})
+        except (TypeError, ValueError) as ex:
+            raise exceptions.EventDeserializationError(
+                f"Failed to deserialize event data: {fields}."
+            ) from ex
 
         self.token_to_sid[event.token] = sid
         self.sid_to_token[sid] = event.token

+ 3 - 4
reflex/app_module_for_backend.py

@@ -7,14 +7,13 @@ from concurrent.futures import ThreadPoolExecutor
 from reflex import constants
 from reflex.utils import telemetry
 from reflex.utils.exec import is_prod_mode
-from reflex.utils.prerequisites import get_app
+from reflex.utils.prerequisites import get_and_validate_app
 
 if constants.CompileVars.APP != "app":
     raise AssertionError("unexpected variable name for 'app'")
 
 telemetry.send("compile")
-app_module = get_app(reload=False)
-app = getattr(app_module, constants.CompileVars.APP)
+app, app_module = get_and_validate_app(reload=False)
 # For py3.9 compatibility when redis is used, we MUST add any decorator pages
 # before compiling the app in a thread to avoid event loop error (REF-2172).
 app._apply_decorated_pages()
@@ -30,7 +29,7 @@ if is_prod_mode():
 # ensure only "app" is exposed.
 del app_module
 del compile_future
-del get_app
+del get_and_validate_app
 del is_prod_mode
 del telemetry
 del constants

+ 19 - 56
reflex/components/component.py

@@ -23,8 +23,6 @@ from typing import (
     Union,
 )
 
-from typing_extensions import deprecated
-
 import reflex.state
 from reflex.base import Base
 from reflex.compiler.templates import STATEFUL_COMPONENT
@@ -47,11 +45,10 @@ from reflex.event import (
     EventChain,
     EventHandler,
     EventSpec,
-    EventVar,
     no_args_event_spec,
 )
 from reflex.style import Style, format_as_emotion
-from reflex.utils import console, format, imports, types
+from reflex.utils import format, imports, types
 from reflex.utils.imports import (
     ImmutableParsedImportDict,
     ImportDict,
@@ -428,20 +425,22 @@ class Component(BaseComponent, ABC):
             else:
                 continue
 
+            def determine_key(value):
+                # Try to create a var from the value
+                key = value if isinstance(value, Var) else LiteralVar.create(value)
+
+                # Check that the var type is not None.
+                if key is None:
+                    raise TypeError
+
+                return key
+
             # Check whether the key is a component prop.
             if types._issubclass(field_type, Var):
                 # Used to store the passed types if var type is a union.
                 passed_types = None
                 try:
-                    # Try to create a var from the value.
-                    if isinstance(value, Var):
-                        kwargs[key] = value
-                    else:
-                        kwargs[key] = LiteralVar.create(value)
-
-                    # Check that the var type is not None.
-                    if kwargs[key] is None:
-                        raise TypeError
+                    kwargs[key] = determine_key(value)
 
                     expected_type = fields[key].outer_type_.__args__[0]
                     # validate literal fields.
@@ -544,41 +543,6 @@ class Component(BaseComponent, ABC):
         # Construct the component.
         super().__init__(*args, **kwargs)
 
-    @deprecated("Use rx.EventChain.create instead.")
-    def _create_event_chain(
-        self,
-        args_spec: types.ArgsSpec | Sequence[types.ArgsSpec],
-        value: Union[
-            Var,
-            EventHandler,
-            EventSpec,
-            List[Union[EventHandler, EventSpec, EventVar]],
-            Callable,
-        ],
-        key: Optional[str] = None,
-    ) -> Union[EventChain, Var]:
-        """Create an event chain from a variety of input types.
-
-        Args:
-            args_spec: The args_spec of the event trigger being bound.
-            value: The value to create the event chain from.
-            key: The key of the event trigger being bound.
-
-        Returns:
-            The event chain.
-        """
-        console.deprecate(
-            "Component._create_event_chain",
-            "Use rx.EventChain.create instead.",
-            deprecation_version="0.6.8",
-            removal_version="0.7.0",
-        )
-        return EventChain.create(
-            value=value,  # type: ignore
-            args_spec=args_spec,
-            key=key,
-        )
-
     def get_event_triggers(
         self,
     ) -> Dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]]:
@@ -739,22 +703,21 @@ class Component(BaseComponent, ABC):
         # Import here to avoid circular imports.
         from reflex.components.base.bare import Bare
         from reflex.components.base.fragment import Fragment
-        from reflex.utils.exceptions import ComponentTypeError
+        from reflex.utils.exceptions import ChildrenTypeError
 
         # Filter out None props
         props = {key: value for key, value in props.items() if value is not None}
 
         def validate_children(children):
             for child in children:
-                if isinstance(child, tuple):
+                if isinstance(child, (tuple, list)):
                     validate_children(child)
+
                 # Make sure the child is a valid type.
-                if not types._isinstance(child, ComponentChild):
-                    raise ComponentTypeError(
-                        "Children of Reflex components must be other components, "
-                        "state vars, or primitive Python types. "
-                        f"Got child {child} of type {type(child)}.",
-                    )
+                if isinstance(child, dict) or not types._isinstance(
+                    child, ComponentChild
+                ):
+                    raise ChildrenTypeError(component=cls.__name__, child=child)
 
         # Validate all the children.
         validate_children(children)

+ 3 - 12
reflex/components/datadisplay/code.py

@@ -14,7 +14,7 @@ from reflex.components.radix.themes.layout.box import Box
 from reflex.constants.colors import Color
 from reflex.event import set_clipboard
 from reflex.style import Style
-from reflex.utils import console, format
+from reflex.utils import format
 from reflex.utils.imports import ImportVar
 from reflex.vars.base import LiteralVar, Var, VarData
 
@@ -438,6 +438,8 @@ class CodeBlock(Component, MarkdownComponentMap):
         can_copy = props.pop("can_copy", False)
         copy_button = props.pop("copy_button", None)
 
+        # react-syntax-highlighter doesn't have an explicit "light" or "dark" theme so we use one-light and one-dark
+        # themes respectively to ensure code compatibility.
         if "theme" not in props:
             # Default color scheme responds to global color mode.
             props["theme"] = color_mode_cond(
@@ -445,17 +447,6 @@ class CodeBlock(Component, MarkdownComponentMap):
                 dark=Theme.one_dark,
             )
 
-        # react-syntax-highlighter doesn't have an explicit "light" or "dark" theme so we use one-light and one-dark
-        # themes respectively to ensure code compatibility.
-        if props.get("theme") is not None and not isinstance(props["theme"], Var):
-            props["theme"] = getattr(Theme, format.to_snake_case(props["theme"]))  # type: ignore
-            console.deprecate(
-                feature_name="theme prop as string",
-                reason="Use code_block.themes instead.",
-                deprecation_version="0.6.0",
-                removal_version="0.7.0",
-            )
-
         if can_copy:
             code = children[0]
             copy_button = (  # type: ignore

+ 2 - 2
reflex/components/el/elements/forms.py

@@ -102,7 +102,7 @@ class Fieldset(Element):
     name: Var[Union[str, int, bool]]
 
 
-def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
+def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]:
     """Event handler spec for the on_submit event.
 
     Returns:
@@ -111,7 +111,7 @@ def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
     return (FORM_DATA,)
 
 
-def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]:
+def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]:
     """Event handler spec for the on_submit event.
 
     Returns:

+ 4 - 4
reflex/components/el/elements/forms.pyi

@@ -270,8 +270,8 @@ class Fieldset(Element):
         """
         ...
 
-def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]: ...
-def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]: ...
+def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]: ...
+def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]: ...
 
 class Form(BaseHTML):
     @overload
@@ -341,10 +341,10 @@ class Form(BaseHTML):
         on_submit: Optional[
             Union[
                 Union[
-                    EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
+                    EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
                 ],
                 Union[
-                    EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
+                    EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
                 ],
             ]
         ] = None,

+ 38 - 12
reflex/components/lucide/icon.py

@@ -2,13 +2,15 @@
 
 from reflex.components.component import Component
 from reflex.utils import format
-from reflex.vars.base import Var
+from reflex.utils.imports import ImportVar
+from reflex.vars.base import LiteralVar, Var
+from reflex.vars.sequence import LiteralStringVar
 
 
 class LucideIconComponent(Component):
     """Lucide Icon Component."""
 
-    library = "lucide-react@0.469.0"
+    library = "lucide-react@0.471.1"
 
 
 class Icon(LucideIconComponent):
@@ -32,6 +34,7 @@ class Icon(LucideIconComponent):
         Raises:
             AttributeError: The errors tied to bad usage of the Icon component.
             ValueError: If the icon tag is invalid.
+            TypeError: If the icon name is not a string.
 
         Returns:
             The created component.
@@ -39,7 +42,6 @@ class Icon(LucideIconComponent):
         if children:
             if len(children) == 1 and isinstance(children[0], str):
                 props["tag"] = children[0]
-                children = []
             else:
                 raise AttributeError(
                     f"Passing multiple children to Icon component is not allowed: remove positional arguments {children[1:]} to fix"
@@ -47,24 +49,46 @@ class Icon(LucideIconComponent):
         if "tag" not in props:
             raise AttributeError("Missing 'tag' keyword-argument for Icon")
 
+        tag: str | Var | LiteralVar = props.pop("tag")
+        if isinstance(tag, LiteralVar):
+            if isinstance(tag, LiteralStringVar):
+                tag = tag._var_value
+            else:
+                raise TypeError(f"Icon name must be a string, got {type(tag)}")
+        elif isinstance(tag, Var):
+            return DynamicIcon.create(name=tag, **props)
+
         if (
-            not isinstance(props["tag"], str)
-            or format.to_snake_case(props["tag"]) not in LUCIDE_ICON_LIST
+            not isinstance(tag, str)
+            or format.to_snake_case(tag) not in LUCIDE_ICON_LIST
         ):
             raise ValueError(
-                f"Invalid icon tag: {props['tag']}. Please use one of the following: {', '.join(LUCIDE_ICON_LIST[0:25])}, ..."
+                f"Invalid icon tag: {tag}. Please use one of the following: {', '.join(LUCIDE_ICON_LIST[0:25])}, ..."
                 "\nSee full list at https://lucide.dev/icons."
             )
 
-        if props["tag"] in LUCIDE_ICON_MAPPING_OVERRIDE:
-            props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[props["tag"]]
+        if tag in LUCIDE_ICON_MAPPING_OVERRIDE:
+            props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[tag]
         else:
-            props["tag"] = (
-                format.to_title_case(format.to_snake_case(props["tag"])) + "Icon"
-            )
+            props["tag"] = format.to_title_case(format.to_snake_case(tag)) + "Icon"
         props["alias"] = f"Lucide{props['tag']}"
         props.setdefault("color", "var(--current-color)")
-        return super().create(*children, **props)
+        return super().create(**props)
+
+
+class DynamicIcon(LucideIconComponent):
+    """A DynamicIcon component."""
+
+    tag = "DynamicIcon"
+
+    name: Var[str]
+
+    def _get_imports(self):
+        _imports = super()._get_imports()
+        if self.library:
+            _imports.pop(self.library)
+        _imports["lucide-react/dynamic"] = [ImportVar("DynamicIcon", install=False)]
+        return _imports
 
 
 LUCIDE_ICON_LIST = [
@@ -846,6 +870,7 @@ LUCIDE_ICON_LIST = [
     "house",
     "house_plug",
     "house_plus",
+    "house_wifi",
     "ice_cream_bowl",
     "ice_cream_cone",
     "id_card",
@@ -1534,6 +1559,7 @@ LUCIDE_ICON_LIST = [
     "trending_up_down",
     "triangle",
     "triangle_alert",
+    "triangle_dashed",
     "triangle_right",
     "trophy",
     "truck",

+ 50 - 0
reflex/components/lucide/icon.pyi

@@ -104,12 +104,60 @@ class Icon(LucideIconComponent):
         Raises:
             AttributeError: The errors tied to bad usage of the Icon component.
             ValueError: If the icon tag is invalid.
+            TypeError: If the icon name is not a string.
 
         Returns:
             The created component.
         """
         ...
 
+class DynamicIcon(LucideIconComponent):
+    @overload
+    @classmethod
+    def create(  # type: ignore
+        cls,
+        *children,
+        name: Optional[Union[Var[str], 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,
+    ) -> "DynamicIcon":
+        """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.
+        """
+        ...
+
 LUCIDE_ICON_LIST = [
     "a_arrow_down",
     "a_arrow_up",
@@ -889,6 +937,7 @@ LUCIDE_ICON_LIST = [
     "house",
     "house_plug",
     "house_plus",
+    "house_wifi",
     "ice_cream_bowl",
     "ice_cream_cone",
     "id_card",
@@ -1577,6 +1626,7 @@ LUCIDE_ICON_LIST = [
     "trending_up_down",
     "triangle",
     "triangle_alert",
+    "triangle_dashed",
     "triangle_right",
     "trophy",
     "truck",

+ 6 - 6
reflex/components/radix/primitives/form.pyi

@@ -132,10 +132,10 @@ class FormRoot(FormComponent, HTMLForm):
         on_submit: Optional[
             Union[
                 Union[
-                    EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
+                    EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
                 ],
                 Union[
-                    EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
+                    EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
                 ],
             ]
         ] = None,
@@ -608,10 +608,10 @@ class Form(FormRoot):
         on_submit: Optional[
             Union[
                 Union[
-                    EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
+                    EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
                 ],
                 Union[
-                    EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
+                    EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
                 ],
             ]
         ] = None,
@@ -741,10 +741,10 @@ class FormNamespace(ComponentNamespace):
         on_submit: Optional[
             Union[
                 Union[
-                    EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
+                    EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
                 ],
                 Union[
-                    EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
+                    EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
                 ],
             ]
         ] = None,

+ 2 - 0
reflex/components/recharts/__init__.py

@@ -70,6 +70,8 @@ _SUBMOD_ATTRS: dict = {
         "Label",
         "label_list",
         "LabelList",
+        "cell",
+        "Cell",
     ],
     "polar": [
         "pie",

+ 2 - 0
reflex/components/recharts/__init__.pyi

@@ -53,11 +53,13 @@ from .charts import radar_chart as radar_chart
 from .charts import radial_bar_chart as radial_bar_chart
 from .charts import scatter_chart as scatter_chart
 from .charts import treemap as treemap
+from .general import Cell as Cell
 from .general import GraphingTooltip as GraphingTooltip
 from .general import Label as Label
 from .general import LabelList as LabelList
 from .general import Legend as Legend
 from .general import ResponsiveContainer as ResponsiveContainer
+from .general import cell as cell
 from .general import graphing_tooltip as graphing_tooltip
 from .general import label as label
 from .general import label_list as label_list

+ 7 - 5
reflex/components/recharts/charts.py

@@ -88,11 +88,13 @@ class ChartBase(RechartsCharts):
             "width": width if width is not None else "100%",
             "height": height if height is not None else "100%",
         }
-        # Provide min dimensions so the graph always appears, even if the outer container is zero-size.
-        if width is None:
-            dim_props["min_width"] = 200
-        if height is None:
-            dim_props["min_height"] = 100
+
+        # Ensure that the min_height and min_width are set to prevent the chart from collapsing.
+        # We are using small values so that height and width can still be used over min_height and min_width.
+        # Without this, sometimes the chart will not be visible. Causing confusion to the user.
+        # With this, the user will see a small chart and can adjust the height and width and can figure out that the issue is with the size.
+        dim_props["min_height"] = props.pop("min_height", 10)
+        dim_props["min_width"] = props.pop("min_width", 10)
 
         return ResponsiveContainer.create(
             super().create(*children, **props),

+ 19 - 4
reflex/components/recharts/general.py

@@ -36,11 +36,11 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
     # The height of chart container. Can be a number or string. Default: "100%"
     height: Var[Union[int, str]]
 
-    # The minimum width of chart container. Number
-    min_width: Var[int]
+    # The minimum width of chart container. Number or string.
+    min_width: Var[Union[int, str]]
 
-    # The minimum height of chart container. Number
-    min_height: Var[int]
+    # The minimum height of chart container. Number or string.
+    min_height: Var[Union[int, str]]
 
     # If specified a positive number, debounced function will be used to handle the resize event. Default: 0
     debounce: Var[int]
@@ -242,8 +242,23 @@ class LabelList(Recharts):
     stroke: Var[Union[str, Color]] = LiteralVar.create("none")
 
 
+class Cell(Recharts):
+    """A Cell component in Recharts."""
+
+    tag = "Cell"
+
+    alias = "RechartsCell"
+
+    # The presentation attribute of a rectangle in bar or a sector in pie.
+    fill: Var[str]
+
+    # The presentation attribute of a rectangle in bar or a sector in pie.
+    stroke: Var[str]
+
+
 responsive_container = ResponsiveContainer.create
 legend = Legend.create
 graphing_tooltip = GraphingTooltip.create
 label = Label.create
 label_list = LabelList.create
+cell = Cell.create

+ 55 - 4
reflex/components/recharts/general.pyi

@@ -22,8 +22,8 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
         aspect: Optional[Union[Var[int], int]] = None,
         width: Optional[Union[Var[Union[int, str]], int, str]] = None,
         height: Optional[Union[Var[Union[int, str]], int, str]] = None,
-        min_width: Optional[Union[Var[int], int]] = None,
-        min_height: Optional[Union[Var[int], int]] = None,
+        min_width: Optional[Union[Var[Union[int, str]], int, str]] = None,
+        min_height: Optional[Union[Var[Union[int, str]], int, str]] = None,
         debounce: Optional[Union[Var[int], int]] = None,
         style: Optional[Style] = None,
         key: Optional[Any] = None,
@@ -56,8 +56,8 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
             aspect: The aspect ratio of the container. The final aspect ratio of the SVG element will be (width / height) * aspect. Number
             width: The width of chart container. Can be a number or string. Default: "100%"
             height: The height of chart container. Can be a number or string. Default: "100%"
-            min_width: The minimum width of chart container. Number
-            min_height: The minimum height of chart container. Number
+            min_width: The minimum width of chart container. Number or string.
+            min_height: The minimum height of chart container. Number or string.
             debounce: If specified a positive number, debounced function will be used to handle the resize event. Default: 0
             on_resize: If specified provides a callback providing the updated chart width and height values.
             style: The style of the component.
@@ -482,8 +482,59 @@ class LabelList(Recharts):
         """
         ...
 
+class Cell(Recharts):
+    @overload
+    @classmethod
+    def create(  # type: ignore
+        cls,
+        *children,
+        fill: Optional[Union[Var[str], str]] = None,
+        stroke: Optional[Union[Var[str], 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,
+    ) -> "Cell":
+        """Create the component.
+
+        Args:
+            *children: The children of the component.
+            fill: The presentation attribute of a rectangle in bar or a sector in pie.
+            stroke: The presentation attribute of a rectangle in bar or a sector in pie.
+            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.
+        """
+        ...
+
 responsive_container = ResponsiveContainer.create
 legend = Legend.create
 graphing_tooltip = GraphingTooltip.create
 label = Label.create
 label_list = LabelList.create
+cell = Cell.create

+ 27 - 3
reflex/config.py

@@ -12,6 +12,7 @@ import threading
 import urllib.parse
 from importlib.util import find_spec
 from pathlib import Path
+from types import ModuleType
 from typing import (
     TYPE_CHECKING,
     Any,
@@ -607,6 +608,9 @@ class Config(Base):
     # The name of the app (should match the name of the app directory).
     app_name: str
 
+    # The path to the app module.
+    app_module_import: Optional[str] = None
+
     # The log level to use.
     loglevel: constants.LogLevel = constants.LogLevel.DEFAULT
 
@@ -729,6 +733,19 @@ class Config(Base):
                 "REDIS_URL is required when using the redis state manager."
             )
 
+    @property
+    def app_module(self) -> ModuleType | None:
+        """Return the app module if `app_module_import` is set.
+
+        Returns:
+            The app module.
+        """
+        return (
+            importlib.import_module(self.app_module_import)
+            if self.app_module_import
+            else None
+        )
+
     @property
     def module(self) -> str:
         """Get the module name of the app.
@@ -736,6 +753,8 @@ class Config(Base):
         Returns:
             The module name.
         """
+        if self.app_module is not None:
+            return self.app_module.__name__
         return ".".join([self.app_name, self.app_name])
 
     def update_from_env(self) -> dict[str, Any]:
@@ -874,7 +893,7 @@ def get_config(reload: bool = False) -> Config:
             return cached_rxconfig.config
 
     with _config_lock:
-        sys_path = sys.path.copy()
+        orig_sys_path = sys.path.copy()
         sys.path.clear()
         sys.path.append(str(Path.cwd()))
         try:
@@ -882,9 +901,14 @@ def get_config(reload: bool = False) -> Config:
             return _get_config()
         except Exception:
             # If the module import fails, try to import with the original sys.path.
-            sys.path.extend(sys_path)
+            sys.path.extend(orig_sys_path)
             return _get_config()
         finally:
+            # Find any entries added to sys.path by rxconfig.py itself.
+            extra_paths = [
+                p for p in sys.path if p not in orig_sys_path and p != str(Path.cwd())
+            ]
             # Restore the original sys.path.
             sys.path.clear()
-            sys.path.extend(sys_path)
+            sys.path.extend(extra_paths)
+            sys.path.extend(orig_sys_path)

+ 1 - 0
reflex/constants/__init__.py

@@ -1,6 +1,7 @@
 """The constants package."""
 
 from .base import (
+    APP_HARNESS_FLAG,
     COOKIES,
     IS_LINUX,
     IS_MACOS,

+ 1 - 0
reflex/constants/base.py

@@ -257,6 +257,7 @@ SESSION_STORAGE = "session_storage"
 # Testing variables.
 # Testing os env set by pytest when running a test case.
 PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
+APP_HARNESS_FLAG = "APP_HARNESS_FLAG"
 
 REFLEX_VAR_OPENING_TAG = "<reflex.Var>"
 REFLEX_VAR_CLOSING_TAG = "</reflex.Var>"

+ 5 - 3
reflex/custom_components/custom_components.py

@@ -421,12 +421,13 @@ def _run_commands_in_subprocess(cmds: list[str]) -> bool:
     console.debug(f"Running command: {' '.join(cmds)}")
     try:
         result = subprocess.run(cmds, capture_output=True, text=True, check=True)
-        console.debug(result.stdout)
-        return True
     except subprocess.CalledProcessError as cpe:
         console.error(cpe.stdout)
         console.error(cpe.stderr)
         return False
+    else:
+        console.debug(result.stdout)
+        return True
 
 
 def _make_pyi_files():
@@ -931,10 +932,11 @@ def _get_file_from_prompt_in_loop() -> Tuple[bytes, str] | None:
         file_extension = image_filepath.suffix
         try:
             image_file = image_filepath.read_bytes()
-            return image_file, file_extension
         except OSError as ose:
             console.error(f"Unable to read the {file_extension} file due to {ose}")
             raise typer.Exit(code=1) from ose
+        else:
+            return image_file, file_extension
 
     console.debug(f"File extension detected: {file_extension}")
     return None

+ 65 - 96
reflex/event.py

@@ -42,7 +42,11 @@ from typing_extensions import (
 from reflex import constants
 from reflex.constants.state import FRONTEND_EVENT_STATE
 from reflex.utils import console, format
-from reflex.utils.exceptions import EventFnArgMismatch, EventHandlerArgTypeMismatch
+from reflex.utils.exceptions import (
+    EventFnArgMismatchError,
+    EventHandlerArgTypeMismatchError,
+    MissingAnnotationError,
+)
 from reflex.utils.types import ArgsSpec, GenericType, typehint_issubclass
 from reflex.vars import VarData
 from reflex.vars.base import LiteralVar, Var
@@ -98,32 +102,6 @@ _EVENT_FIELDS: set[str] = {f.name for f in dataclasses.fields(Event)}
 BACKGROUND_TASK_MARKER = "_reflex_background_task"
 
 
-def background(fn, *, __internal_reflex_call: bool = False):
-    """Decorator to mark event handler as running in the background.
-
-    Args:
-        fn: The function to decorate.
-
-    Returns:
-        The same function, but with a marker set.
-
-
-    Raises:
-        TypeError: If the function is not a coroutine function or async generator.
-    """
-    if not __internal_reflex_call:
-        console.deprecate(
-            "background-decorator",
-            "Use `rx.event(background=True)` instead.",
-            "0.6.5",
-            "0.7.0",
-        )
-    if not inspect.iscoroutinefunction(fn) and not inspect.isasyncgenfunction(fn):
-        raise TypeError("Background task must be async function or generator.")
-    setattr(fn, BACKGROUND_TASK_MARKER, True)
-    return fn
-
-
 @dataclasses.dataclass(
     init=True,
     frozen=True,
@@ -821,10 +799,9 @@ def server_side(name: str, sig: inspect.Signature, **kwargs) -> EventSpec:
     )
 
 
-@overload
 def redirect(
     path: str | Var[str],
-    is_external: Optional[bool] = None,
+    is_external: bool = False,
     replace: bool = False,
 ) -> EventSpec: ...
 
@@ -851,26 +828,10 @@ def redirect(
         path: The path to redirect to.
         is_external: Whether to open in new tab or not.
         replace: If True, the current page will not create a new history entry.
-        external(Deprecated): Whether to open in new tab or not.
 
     Returns:
         An event to redirect to the path.
     """
-    if external is not None:
-        console.deprecate(
-            "The `external` prop in `rx.redirect`",
-            "use `is_external` instead.",
-            "0.6.6",
-            "0.7.0",
-        )
-
-    # is_external should take precedence over external.
-    is_external = (
-        (False if external is None else external)
-        if is_external is None
-        else is_external
-    )
-
     return server_side(
         "_redirect",
         get_fn_signature(redirect),
@@ -1287,11 +1248,14 @@ def call_event_handler(
         event_spec: The lambda that define the argument(s) to pass to the event handler.
         key: The key to pass to the event handler.
 
+    Raises:
+        EventHandlerArgTypeMismatchError: If the event handler arguments do not match the event spec. #noqa: DAR402
+        TypeError: If the event handler arguments are invalid.
+
     Returns:
         The event spec from calling the event handler.
 
-    # noqa: DAR401 failure
-
+    #noqa: DAR401
     """
     event_spec_args = parse_args_spec(event_spec)  # type: ignore
 
@@ -1328,10 +1292,15 @@ def call_event_handler(
             ),
         )
     )
+    type_match_found: dict[str, bool] = {}
+    delayed_exceptions: list[EventHandlerArgTypeMismatchError] = []
 
-    if event_spec_return_types:
-        failures = []
+    try:
+        type_hints_of_provided_callback = get_type_hints(event_callback.fn)
+    except NameError:
+        type_hints_of_provided_callback = {}
 
+    if event_spec_return_types:
         event_callback_spec = inspect.getfullargspec(event_callback.fn)
 
         for event_spec_index, event_spec_return_type in enumerate(
@@ -1343,43 +1312,35 @@ def call_event_handler(
                 arg if get_origin(arg) is not Var else get_args(arg)[0] for arg in args
             ]
 
-            try:
-                type_hints_of_provided_callback = get_type_hints(event_callback.fn)
-            except NameError:
-                type_hints_of_provided_callback = {}
-
-            failed_type_check = False
-
             # check that args of event handler are matching the spec if type hints are provided
             for i, arg in enumerate(event_callback_spec.args[1:]):
                 if arg not in type_hints_of_provided_callback:
                     continue
 
+                type_match_found.setdefault(arg, False)
+
                 try:
                     compare_result = typehint_issubclass(
                         args_types_without_vars[i], type_hints_of_provided_callback[arg]
                     )
-                except TypeError:
-                    # TODO: In 0.7.0, remove this block and raise the exception
-                    # raise TypeError(
-                    #     f"Could not compare types {args_types_without_vars[i]} and {type_hints_of_provided_callback[arg]} for argument {arg} of {event_handler.fn.__qualname__} provided for {key}." # noqa: ERA001
-                    # ) from e
-                    console.warn(
+                except TypeError as te:
+                    raise TypeError(
                         f"Could not compare types {args_types_without_vars[i]} and {type_hints_of_provided_callback[arg]} for argument {arg} of {event_callback.fn.__qualname__} provided for {key}."
-                    )
-                    compare_result = False
+                    ) from te
 
                 if compare_result:
+                    type_match_found[arg] = True
                     continue
                 else:
-                    failure = EventHandlerArgTypeMismatch(
-                        f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {type_hints_of_provided_callback[arg]} as annotated in {event_callback.fn.__qualname__} instead."
+                    type_match_found[arg] = False
+                    delayed_exceptions.append(
+                        EventHandlerArgTypeMismatchError(
+                            f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {type_hints_of_provided_callback[arg]} as annotated in {event_callback.fn.__qualname__} instead."
+                        )
                     )
-                    failures.append(failure)
-                    failed_type_check = True
-                    break
 
-            if not failed_type_check:
+            if all(type_match_found.values()):
+                delayed_exceptions.clear()
                 if event_spec_index:
                     args = get_args(event_spec_return_types[0])
 
@@ -1401,15 +1362,10 @@ def call_event_handler(
                         f"Event handler {key} expects ({expect_string}) -> () but got ({given_string}) -> () as annotated in {event_callback.fn.__qualname__} instead. "
                         f"This may lead to unexpected behavior but is intentionally ignored for {key}."
                     )
-                return event_callback(*event_spec_args)
-
-        if failures:
-            console.deprecate(
-                "Mismatched event handler argument types",
-                "\n".join([str(f) for f in failures]),
-                "0.6.5",
-                "0.7.0",
-            )
+                break
+
+    if delayed_exceptions:
+        raise delayed_exceptions[0]
 
     return event_callback(*event_spec_args)  # type: ignore
 
@@ -1428,26 +1384,26 @@ def unwrap_var_annotation(annotation: GenericType):
     return annotation
 
 
-def resolve_annotation(annotations: dict[str, Any], arg_name: str):
+def resolve_annotation(annotations: dict[str, Any], arg_name: str, spec: ArgsSpec):
     """Resolve the annotation for the given argument name.
 
     Args:
         annotations: The annotations.
         arg_name: The argument name.
+        spec: The specs which the annotations come from.
+
+    Raises:
+        MissingAnnotationError: If the annotation is missing for non-lambda methods.
 
     Returns:
         The resolved annotation.
     """
     annotation = annotations.get(arg_name)
     if annotation is None:
-        console.deprecate(
-            feature_name="Unannotated event handler arguments",
-            reason="Provide type annotations for event handler arguments.",
-            deprecation_version="0.6.3",
-            removal_version="0.7.0",
-        )
-        # Allow arbitrary attribute access two levels deep until removed.
-        return Dict[str, dict]
+        if not isinstance(spec, types.LambdaType):
+            raise MissingAnnotationError(var_name=arg_name)
+        else:
+            return dict[str, dict]
     return annotation
 
 
@@ -1469,7 +1425,13 @@ def parse_args_spec(arg_spec: ArgsSpec | Sequence[ArgsSpec]):
         arg_spec(
             *[
                 Var(f"_{l_arg}").to(
-                    unwrap_var_annotation(resolve_annotation(annotations, l_arg))
+                    unwrap_var_annotation(
+                        resolve_annotation(
+                            annotations,
+                            l_arg,
+                            spec=arg_spec,
+                        )
+                    )
                 )
                 for l_arg in spec.args
             ]
@@ -1485,7 +1447,7 @@ def check_fn_match_arg_spec(
     func_name: str | None = None,
 ):
     """Ensures that the function signature matches the passed argument specification
-    or raises an EventFnArgMismatch if they do not.
+    or raises an EventFnArgMismatchError if they do not.
 
     Args:
         user_func: The function to be validated.
@@ -1495,7 +1457,7 @@ def check_fn_match_arg_spec(
         func_name: The name of the function to be validated.
 
     Raises:
-        EventFnArgMismatch: Raised if the number of mandatory arguments do not match
+        EventFnArgMismatchError: Raised if the number of mandatory arguments do not match
     """
     user_args = inspect.getfullargspec(user_func).args
     # Drop the first argument if it's a bound method
@@ -1511,7 +1473,7 @@ def check_fn_match_arg_spec(
     number_of_event_args = len(parsed_event_args)
 
     if number_of_user_args - number_of_user_default_args > number_of_event_args:
-        raise EventFnArgMismatch(
+        raise EventFnArgMismatchError(
             f"Event {key} only provides {number_of_event_args} arguments, but "
             f"{func_name or user_func} requires at least {number_of_user_args - number_of_user_default_args} "
             "arguments to be passed to the event handler.\n"
@@ -1599,7 +1561,7 @@ def get_handler_args(
 
 
 def fix_events(
-    events: list[EventHandler | EventSpec] | None,
+    events: list[EventSpec | EventHandler] | None,
     token: str,
     router_data: dict[str, Any] | None = None,
 ) -> list[Event]:
@@ -1820,8 +1782,6 @@ V3 = TypeVar("V3")
 V4 = TypeVar("V4")
 V5 = TypeVar("V5")
 
-background_event_decorator = background
-
 
 class EventCallback(Generic[P, T]):
     """A descriptor that wraps a function to be used as an event."""
@@ -2025,6 +1985,9 @@ class EventNamespace(types.SimpleNamespace):
             func: The function to wrap.
             background: Whether the event should be run in the background. Defaults to False.
 
+        Raises:
+            TypeError: If background is True and the function is not a coroutine or async generator. # noqa: DAR402
+
         Returns:
             The wrapped function.
         """
@@ -2033,7 +1996,13 @@ class EventNamespace(types.SimpleNamespace):
             func: Callable[Concatenate[BASE_STATE, P], T],
         ) -> EventCallback[P, T]:
             if background is True:
-                return background_event_decorator(func, __internal_reflex_call=True)  # type: ignore
+                if not inspect.iscoroutinefunction(
+                    func
+                ) and not inspect.isasyncgenfunction(func):
+                    raise TypeError(
+                        "Background task must be async function or generator."
+                    )
+                setattr(func, BACKGROUND_TASK_MARKER, True)
             return func  # type: ignore
 
         if func is not None:

+ 0 - 2
reflex/experimental/__init__.py

@@ -9,7 +9,6 @@ from reflex.components.sonner.toast import toast as toast
 
 from ..utils.console import warn
 from . import hooks as hooks
-from .assets import asset as asset
 from .client_state import ClientStateVar as ClientStateVar
 from .layout import layout as layout
 from .misc import run_in_thread as run_in_thread
@@ -62,7 +61,6 @@ class ExperimentalNamespace(SimpleNamespace):
 
 
 _x = ExperimentalNamespace(
-    asset=asset,
     client_state=ClientStateVar.create,
     hooks=hooks,
     layout=layout,

+ 0 - 37
reflex/experimental/assets.py

@@ -1,37 +0,0 @@
-"""Helper functions for adding assets to the app."""
-
-from typing import Optional
-
-from reflex import assets
-from reflex.utils import console
-
-
-def asset(relative_filename: str, subfolder: Optional[str] = None) -> str:
-    """DEPRECATED: use `rx.asset` with `shared=True` instead.
-
-    Add an asset to the app.
-    Place the file next to your including python file.
-    Copies the file to the app's external assets directory.
-
-    Example:
-    ```python
-    rx.script(src=rx._x.asset("my_custom_javascript.js"))
-    rx.image(src=rx._x.asset("test_image.png","subfolder"))
-    ```
-
-    Args:
-        relative_filename: The relative filename of the asset.
-        subfolder: The directory to place the asset in.
-
-    Returns:
-        The relative URL to the copied asset.
-    """
-    console.deprecate(
-        feature_name="rx._x.asset",
-        reason="Use `rx.asset` with `shared=True` instead of `rx._x.asset`.",
-        deprecation_version="0.6.6",
-        removal_version="0.7.0",
-    )
-    return assets.asset(
-        relative_filename, shared=True, subfolder=subfolder, _stack_level=2
-    )

+ 34 - 12
reflex/reflex.py

@@ -306,6 +306,9 @@ def export(
         help="Whether to exclude sqlite db files when exporting backend.",
         hidden=True,
     ),
+    env: constants.Env = typer.Option(
+        constants.Env.PROD, help="The environment to export the app in."
+    ),
     loglevel: constants.LogLevel = typer.Option(
         config.loglevel, help="The log level to use."
     ),
@@ -323,6 +326,7 @@ def export(
         backend=backend,
         zip_dest_dir=zip_dest_dir,
         upload_db_file=upload_db_file,
+        env=env,
         loglevel=loglevel.subprocess_level(),
     )
 
@@ -440,7 +444,11 @@ def deploy(
         config.app_name,
         "--app-name",
         help="The name of the App to deploy under.",
-        hidden=True,
+    ),
+    app_id: str = typer.Option(
+        None,
+        "--app-id",
+        help="The ID of the App to deploy over.",
     ),
     regions: List[str] = typer.Option(
         [],
@@ -480,6 +488,11 @@ def deploy(
         "--project",
         help="project id to deploy to",
     ),
+    project_name: Optional[str] = typer.Option(
+        None,
+        "--project-name",
+        help="The name of the project to deploy to.",
+    ),
     token: Optional[str] = typer.Option(
         None,
         "--token",
@@ -492,6 +505,7 @@ def deploy(
     ),
 ):
     """Deploy the app to the Reflex hosting service."""
+    from reflex_cli.constants.base import LogLevel as HostingLogLevel
     from reflex_cli.utils import dependency
     from reflex_cli.v2 import cli as hosting_cli
 
@@ -503,12 +517,20 @@ def deploy(
     # Set the log level.
     console.set_log_level(loglevel)
 
-    if not token:
-        # make sure user is logged in.
-        if interactive:
-            hosting_cli.login()
-        else:
-            raise SystemExit("Token is required for non-interactive mode.")
+    def convert_reflex_loglevel_to_reflex_cli_loglevel(
+        loglevel: constants.LogLevel,
+    ) -> HostingLogLevel:
+        if loglevel == constants.LogLevel.DEBUG:
+            return HostingLogLevel.DEBUG
+        if loglevel == constants.LogLevel.INFO:
+            return HostingLogLevel.INFO
+        if loglevel == constants.LogLevel.WARNING:
+            return HostingLogLevel.WARNING
+        if loglevel == constants.LogLevel.ERROR:
+            return HostingLogLevel.ERROR
+        if loglevel == constants.LogLevel.CRITICAL:
+            return HostingLogLevel.CRITICAL
+        return HostingLogLevel.INFO
 
     # Only check requirements if interactive.
     # There is user interaction for requirements update.
@@ -519,11 +541,10 @@ def deploy(
     if prerequisites.needs_reinit(frontend=True):
         _init(name=config.app_name, loglevel=loglevel)
     prerequisites.check_latest_package_version(constants.ReflexHostingCLI.MODULE_NAME)
-    extra: dict[str, str] = (
-        {"config_path": config_path} if config_path is not None else {}
-    )
+
     hosting_cli.deploy(
         app_name=app_name,
+        app_id=app_id,
         export_fn=lambda zip_dest_dir,
         api_url,
         deploy_url,
@@ -544,10 +565,11 @@ def deploy(
         envfile=envfile,
         hostname=hostname,
         interactive=interactive,
-        loglevel=type(loglevel).INFO,  # type: ignore
+        loglevel=convert_reflex_loglevel_to_reflex_cli_loglevel(loglevel),
         token=token,
         project=project,
-        **extra,
+        project_name=project_name,
+        **({"config_path": config_path} if config_path is not None else {}),
     )
 
 

+ 19 - 21
reflex/state.py

@@ -1341,12 +1341,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             if field.allow_none and not is_optional(field_type):
                 field_type = Union[field_type, None]
             if not _isinstance(value, field_type):
-                console.deprecate(
-                    "mismatched-type-assignment",
-                    f"Tried to assign value {value} of type {type(value)} to field {type(self).__name__}.{name} of type {field_type}."
-                    " This might lead to unexpected behavior.",
-                    "0.6.5",
-                    "0.7.0",
+                console.error(
+                    f"Expected field '{type(self).__name__}.{name}' to receive type '{field_type}',"
+                    f" but got '{value}' of type '{type(value)}'."
                 )
 
         # Set the attribute.
@@ -1776,9 +1773,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         except Exception as ex:
             state._clean()
 
-            app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
-
-            event_specs = app_instance.backend_exception_handler(ex)
+            event_specs = (
+                prerequisites.get_and_validate_app().app.backend_exception_handler(ex)
+            )
 
             if event_specs is None:
                 return StateUpdate()
@@ -1831,7 +1828,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             if (
                 isinstance(value, dict)
                 and inspect.isclass(hinted_args)
-                and not types.is_generic_alias(hinted_args)  # py3.9-py3.10
+                and not types.is_generic_alias(hinted_args)  # py3.10
             ):
                 if issubclass(hinted_args, Model):
                     # Remove non-fields from the payload
@@ -1888,9 +1885,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         except Exception as ex:
             telemetry.send_error(ex, context="backend")
 
-            app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
-
-            event_specs = app_instance.backend_exception_handler(ex)
+            event_specs = (
+                prerequisites.get_and_validate_app().app.backend_exception_handler(ex)
+            )
 
             yield state._as_state_update(
                 handler,
@@ -2403,8 +2400,9 @@ class FrontendEventExceptionState(State):
             component_stack: The stack trace of the component where the exception occurred.
 
         """
-        app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
-        app_instance.frontend_exception_handler(Exception(stack))
+        prerequisites.get_and_validate_app().app.frontend_exception_handler(
+            Exception(stack)
+        )
 
 
 class UpdateVarsInternalState(State):
@@ -2442,15 +2440,16 @@ class OnLoadInternalState(State):
             The list of events to queue for on load handling.
         """
         # Do not app._compile()!  It should be already compiled by now.
-        app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
-        load_events = app.get_load_events(self.router.page.path)
+        load_events = prerequisites.get_and_validate_app().app.get_load_events(
+            self.router.page.path
+        )
         if not load_events:
             self.is_hydrated = True
             return  # Fast path for navigation with no on_load events defined.
         self.is_hydrated = False
         return [
             *fix_events(
-                load_events,
+                cast(list[Union[EventSpec, EventHandler]], load_events),
                 self.router.session.client_token,
                 router_data=self.router_data,
             ),
@@ -2612,7 +2611,7 @@ class StateProxy(wrapt.ObjectProxy):
         """
         super().__init__(state_instance)
         # compile is not relevant to backend logic
-        self._self_app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
+        self._self_app = prerequisites.get_and_validate_app().app
         self._self_substate_path = tuple(state_instance.get_full_name().split("."))
         self._self_actx = None
         self._self_mutable = False
@@ -3705,8 +3704,7 @@ def get_state_manager() -> StateManager:
     Returns:
         The state manager.
     """
-    app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
-    return app.state_manager
+    return prerequisites.get_and_validate_app().app.state_manager
 
 
 DATACLASS_FIELDS = getattr(dataclasses, "_FIELDS", "__dataclass_fields__")

+ 3 - 1
reflex/style.py

@@ -287,7 +287,9 @@ class Style(dict):
         _var = LiteralVar.create(value)
         if _var is not None:
             # Carry the imports/hooks when setting a Var as a value.
-            self._var_data = VarData.merge(self._var_data, _var._get_all_var_data())
+            self._var_data = VarData.merge(
+                getattr(self, "_var_data", None), _var._get_all_var_data()
+            )
         super().__setitem__(key, value)
 
 

+ 2 - 0
reflex/testing.py

@@ -280,6 +280,7 @@ class AppHarness:
             before_decorated_pages = reflex.app.DECORATED_PAGES[self.app_name].copy()
             # Ensure the AppHarness test does not skip State assignment due to running via pytest
             os.environ.pop(reflex.constants.PYTEST_CURRENT_TEST, None)
+            os.environ[reflex.constants.APP_HARNESS_FLAG] = "true"
             self.app_module = reflex.utils.prerequisites.get_compiled_app(
                 # Do not reload the module for pre-existing apps (only apps generated from source)
                 reload=self.app_source is not None
@@ -934,6 +935,7 @@ class AppHarnessProd(AppHarness):
                 frontend=True,
                 backend=False,
                 loglevel=reflex.constants.LogLevel.INFO,
+                env=reflex.constants.Env.PROD,
             )
 
         self.frontend_thread = threading.Thread(target=self._run_frontend)

+ 5 - 1
reflex/utils/build.py

@@ -13,13 +13,17 @@ from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
 from reflex import constants
 from reflex.config import get_config
 from reflex.utils import console, path_ops, prerequisites, processes
+from reflex.utils.exec import is_in_app_harness
 
 
 def set_env_json():
     """Write the upload url to a REFLEX_JSON."""
     path_ops.update_json_file(
         str(prerequisites.get_web_dir() / constants.Dirs.ENV_JSON),
-        {endpoint.name: endpoint.get_url() for endpoint in constants.Endpoint},
+        {
+            **{endpoint.name: endpoint.get_url() for endpoint in constants.Endpoint},
+            "TEST_MODE": is_in_app_harness(),
+        },
     )
 
 

+ 3 - 11
reflex/utils/console.py

@@ -51,20 +51,12 @@ def set_log_level(log_level: LogLevel):
         log_level: The log level to set.
 
     Raises:
-        ValueError: If the log level is invalid.
+        TypeError: If the log level is a string.
     """
     if not isinstance(log_level, LogLevel):
-        deprecate(
-            feature_name="Passing a string to set_log_level",
-            reason="use reflex.constants.LogLevel enum instead",
-            deprecation_version="0.6.6",
-            removal_version="0.7.0",
+        raise TypeError(
+            f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead."
         )
-        try:
-            log_level = getattr(LogLevel, log_level.upper())
-        except AttributeError as ae:
-            raise ValueError(f"Invalid log level: {log_level}") from ae
-
     global _LOG_LEVEL
     _LOG_LEVEL = log_level
 

+ 58 - 16
reflex/utils/exceptions.py

@@ -1,6 +1,6 @@
 """Custom Exceptions."""
 
-from typing import NoReturn
+from typing import Any
 
 
 class ReflexError(Exception):
@@ -31,6 +31,22 @@ class ComponentTypeError(ReflexError, TypeError):
     """Custom TypeError for component related errors."""
 
 
+class ChildrenTypeError(ComponentTypeError):
+    """Raised when the children prop of a component is not a valid type."""
+
+    def __init__(self, component: str, child: Any):
+        """Initialize the exception.
+
+        Args:
+            component: The name of the component.
+            child: The child that caused the error.
+        """
+        super().__init__(
+            f"Component {component} received child {child} of type {type(child)}. "
+            "Accepted types are other components, state vars, or primitive Python types (dict excluded)."
+        )
+
+
 class EventHandlerTypeError(ReflexError, TypeError):
     """Custom TypeError for event handler related errors."""
 
@@ -59,6 +75,30 @@ class VarAttributeError(ReflexError, AttributeError):
     """Custom AttributeError for var related errors."""
 
 
+class UntypedComputedVarError(ReflexError, TypeError):
+    """Custom TypeError for untyped computed var errors."""
+
+    def __init__(self, var_name):
+        """Initialize the UntypedComputedVarError.
+
+        Args:
+            var_name: The name of the computed var.
+        """
+        super().__init__(f"Computed var '{var_name}' must have a type annotation.")
+
+
+class MissingAnnotationError(ReflexError, TypeError):
+    """Custom TypeError for missing annotations."""
+
+    def __init__(self, var_name):
+        """Initialize the MissingAnnotationError.
+
+        Args:
+            var_name: The name of the var.
+        """
+        super().__init__(f"Var '{var_name}' must have a type annotation.")
+
+
 class UploadValueError(ReflexError, ValueError):
     """Custom ValueError for upload related errors."""
 
@@ -95,11 +135,11 @@ class MatchTypeError(ReflexError, TypeError):
     """Raised when the return types of match cases are different."""
 
 
-class EventHandlerArgTypeMismatch(ReflexError, TypeError):
+class EventHandlerArgTypeMismatchError(ReflexError, TypeError):
     """Raised when the annotations of args accepted by an EventHandler differs from the spec of the event trigger."""
 
 
-class EventFnArgMismatch(ReflexError, TypeError):
+class EventFnArgMismatchError(ReflexError, TypeError):
     """Raised when the number of args required by an event handler is more than provided by the event trigger."""
 
 
@@ -170,23 +210,25 @@ class StateMismatchError(ReflexError, ValueError):
 class SystemPackageMissingError(ReflexError):
     """Raised when a system package is missing."""
 
+    def __init__(self, package: str):
+        """Initialize the SystemPackageMissingError.
 
-def raise_system_package_missing_error(package: str) -> NoReturn:
-    """Raise a SystemPackageMissingError.
+        Args:
+            package: The missing package.
+        """
+        from reflex.constants import IS_MACOS
 
-    Args:
-        package: The name of the missing system package.
+        extra = (
+            f" You can do so by running 'brew install {package}'." if IS_MACOS else ""
+        )
+        super().__init__(
+            f"System package '{package}' is missing."
+            f" Please install it through your system package manager.{extra}"
+        )
 
-    Raises:
-        SystemPackageMissingError: The raised exception.
-    """
-    from reflex.constants import IS_MACOS
 
-    raise SystemPackageMissingError(
-        f"System package '{package}' is missing."
-        " Please install it through your system package manager."
-        + (f" You can do so by running 'brew install {package}'." if IS_MACOS else "")
-    )
+class EventDeserializationError(ReflexError, ValueError):
+    """Raised when an event cannot be deserialized."""
 
 
 class InvalidLockWarningThresholdError(ReflexError):

+ 36 - 47
reflex/utils/exec.py

@@ -240,6 +240,31 @@ def run_backend(
         run_uvicorn_backend(host, port, loglevel)
 
 
+def get_reload_dirs() -> list[Path]:
+    """Get the reload directories for the backend.
+
+    Returns:
+        The reload directories for the backend.
+    """
+    config = get_config()
+    reload_dirs = [Path(config.app_name)]
+    if config.app_module is not None and config.app_module.__file__:
+        module_path = Path(config.app_module.__file__).resolve().parent
+
+        while module_path.parent.name:
+            if any(
+                sibling_file.name == "__init__.py"
+                for sibling_file in module_path.parent.iterdir()
+            ):
+                # go up a level to find dir without `__init__.py`
+                module_path = module_path.parent
+            else:
+                break
+
+        reload_dirs = [module_path]
+    return reload_dirs
+
+
 def run_uvicorn_backend(host, port, loglevel: LogLevel):
     """Run the backend in development mode using Uvicorn.
 
@@ -256,7 +281,7 @@ def run_uvicorn_backend(host, port, loglevel: LogLevel):
         port=port,
         log_level=loglevel.value,
         reload=True,
-        reload_dirs=[get_config().app_name],
+        reload_dirs=list(map(str, get_reload_dirs())),
     )
 
 
@@ -281,7 +306,7 @@ def run_granian_backend(host, port, loglevel: LogLevel):
             interface=Interfaces.ASGI,
             log_level=LogLevels(loglevel.value),
             reload=True,
-            reload_paths=[Path(get_config().app_name)],
+            reload_paths=get_reload_dirs(),
             reload_ignore_dirs=[".web"],
         ).serve()
     except ImportError:
@@ -487,56 +512,20 @@ def is_testing_env() -> bool:
     return constants.PYTEST_CURRENT_TEST in os.environ
 
 
-def is_prod_mode() -> bool:
-    """Check if the app is running in production mode.
+def is_in_app_harness() -> bool:
+    """Whether the app is running in the app harness.
 
     Returns:
-        True if the app is running in production mode or False if running in dev mode.
+        True if the app is running in the app harness.
     """
-    current_mode = environment.REFLEX_ENV_MODE.get()
-    return current_mode == constants.Env.PROD
-
+    return constants.APP_HARNESS_FLAG in os.environ
 
-def is_frontend_only() -> bool:
-    """Check if the app is running in frontend-only mode.
 
-    Returns:
-        True if the app is running in frontend-only mode.
-    """
-    console.deprecate(
-        "is_frontend_only() is deprecated and will be removed in a future release.",
-        reason="Use `environment.REFLEX_FRONTEND_ONLY.get()` instead.",
-        deprecation_version="0.6.5",
-        removal_version="0.7.0",
-    )
-    return environment.REFLEX_FRONTEND_ONLY.get()
-
-
-def is_backend_only() -> bool:
-    """Check if the app is running in backend-only mode.
-
-    Returns:
-        True if the app is running in backend-only mode.
-    """
-    console.deprecate(
-        "is_backend_only() is deprecated and will be removed in a future release.",
-        reason="Use `environment.REFLEX_BACKEND_ONLY.get()` instead.",
-        deprecation_version="0.6.5",
-        removal_version="0.7.0",
-    )
-    return environment.REFLEX_BACKEND_ONLY.get()
-
-
-def should_skip_compile() -> bool:
-    """Whether the app should skip compile.
+def is_prod_mode() -> bool:
+    """Check if the app is running in production mode.
 
     Returns:
-        True if the app should skip compile.
+        True if the app is running in production mode or False if running in dev mode.
     """
-    console.deprecate(
-        "should_skip_compile() is deprecated and will be removed in a future release.",
-        reason="Use `environment.REFLEX_SKIP_COMPILE.get()` instead.",
-        deprecation_version="0.6.5",
-        removal_version="0.7.0",
-    )
-    return environment.REFLEX_SKIP_COMPILE.get()
+    current_mode = environment.REFLEX_ENV_MODE.get()
+    return current_mode == constants.Env.PROD

+ 6 - 1
reflex/utils/export.py

@@ -4,7 +4,7 @@ from pathlib import Path
 from typing import Optional
 
 from reflex import constants
-from reflex.config import get_config
+from reflex.config import environment, get_config
 from reflex.utils import build, console, exec, prerequisites, telemetry
 
 config = get_config()
@@ -18,6 +18,7 @@ def export(
     upload_db_file: bool = False,
     api_url: Optional[str] = None,
     deploy_url: Optional[str] = None,
+    env: constants.Env = constants.Env.PROD,
     loglevel: constants.LogLevel = console._LOG_LEVEL,
 ):
     """Export the app to a zip file.
@@ -30,11 +31,15 @@ def export(
         upload_db_file: Whether to upload the database file. Defaults to False.
         api_url: The API URL to use. Defaults to None.
         deploy_url: The deploy URL to use. Defaults to None.
+        env: The environment to use. Defaults to constants.Env.PROD.
         loglevel: The log level to use. Defaults to console._LOG_LEVEL.
     """
     # Set the log level.
     console.set_log_level(loglevel)
 
+    # Set env mode in the environment
+    environment.REFLEX_ENV_MODE.set(env)
+
     # Override the config url values if provided.
     if api_url is not None:
         config.api_url = str(api_url)

+ 0 - 32
reflex/utils/format.py

@@ -10,7 +10,6 @@ from typing import TYPE_CHECKING, Any, Optional, Union
 from reflex import constants
 from reflex.constants.state import FRONTEND_EVENT_STATE
 from reflex.utils import exceptions
-from reflex.utils.console import deprecate
 
 if TYPE_CHECKING:
     from reflex.components.component import ComponentStyle
@@ -377,37 +376,6 @@ if TYPE_CHECKING:
     from reflex.vars import Var
 
 
-def format_event_chain(
-    event_chain: EventChain | Var[EventChain],
-    event_arg: Var | None = None,
-) -> str:
-    """DEPRECATED: format an event chain as a javascript invocation.
-
-    Use str(rx.Var.create(event_chain)) instead.
-
-    Args:
-        event_chain: The event chain to format.
-        event_arg: this argument is ignored.
-
-    Returns:
-        Compiled javascript code to queue the given event chain on the frontend.
-    """
-    deprecate(
-        feature_name="format_event_chain",
-        reason="Use str(rx.Var.create(event_chain)) instead",
-        deprecation_version="0.6.0",
-        removal_version="0.7.0",
-    )
-
-    from reflex.vars import Var
-    from reflex.vars.function import ArgsFunctionOperation
-
-    result = Var.create(event_chain)
-    if isinstance(result, ArgsFunctionOperation):
-        result = result._return_expr
-    return str(result)
-
-
 def format_queue_events(
     events: EventType | None = None,
     args_spec: Optional[ArgsSpec] = None,

+ 3 - 3
reflex/utils/path_ops.py

@@ -174,7 +174,7 @@ def get_node_path() -> str | None:
     return str(node_path)
 
 
-def get_npm_path() -> str | None:
+def get_npm_path() -> Path | None:
     """Get npm binary path.
 
     Returns:
@@ -183,8 +183,8 @@ def get_npm_path() -> str | None:
     npm_path = Path(constants.Node.NPM_PATH)
     if use_system_node() or not npm_path.exists():
         system_npm_path = which("npm")
-        return str(system_npm_path) if system_npm_path else None
-    return str(npm_path)
+        npm_path = Path(system_npm_path) if system_npm_path else None
+    return npm_path.absolute() if npm_path else None
 
 
 def update_json_file(file_path: str | Path, update_dict: dict[str, int | str]):

+ 82 - 38
reflex/utils/prerequisites.py

@@ -17,11 +17,12 @@ import stat
 import sys
 import tempfile
 import time
+import typing
 import zipfile
 from datetime import datetime
 from pathlib import Path
 from types import ModuleType
-from typing import Callable, List, Optional
+from typing import Callable, List, NamedTuple, Optional
 
 import httpx
 import typer
@@ -37,14 +38,24 @@ from reflex.config import Config, environment, get_config
 from reflex.utils import console, net, path_ops, processes, redir
 from reflex.utils.exceptions import (
     GeneratedCodeHasNoFunctionDefs,
-    raise_system_package_missing_error,
+    SystemPackageMissingError,
 )
 from reflex.utils.format import format_library_name
 from reflex.utils.registry import _get_npm_registry
 
+if typing.TYPE_CHECKING:
+    from reflex.app import App
+
 CURRENTLY_INSTALLING_NODE = False
 
 
+class AppInfo(NamedTuple):
+    """A tuple containing the app instance and module."""
+
+    app: App
+    module: ModuleType
+
+
 @dataclasses.dataclass(frozen=True)
 class Template:
     """A template for a Reflex app."""
@@ -75,18 +86,6 @@ def get_web_dir() -> Path:
     return environment.REFLEX_WEB_WORKDIR.get()
 
 
-def _python_version_check():
-    """Emit deprecation warning for deprecated python versions."""
-    # Check for end-of-life python versions.
-    if sys.version_info < (3, 10):
-        console.deprecate(
-            feature_name="Support for Python 3.9 and older",
-            reason="please upgrade to Python 3.10 or newer",
-            deprecation_version="0.6.0",
-            removal_version="0.7.0",
-        )
-
-
 def check_latest_package_version(package_name: str):
     """Check if the latest version of the package is installed.
 
@@ -109,8 +108,6 @@ def check_latest_package_version(package_name: str):
             console.warn(
                 f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
             )
-        # Check for deprecated python versions
-        _python_version_check()
     except Exception:
         pass
 
@@ -243,7 +240,7 @@ def get_package_manager(on_failure_return_none: bool = False) -> str | None:
     """
     npm_path = path_ops.get_npm_path()
     if npm_path is not None:
-        return str(Path(npm_path).resolve())
+        return str(npm_path)
     if on_failure_return_none:
         return None
     raise FileNotFoundError("NPM not found. You may need to run `reflex init`.")
@@ -267,6 +264,22 @@ def windows_npm_escape_hatch() -> bool:
     return environment.REFLEX_USE_NPM.get()
 
 
+def _check_app_name(config: Config):
+    """Check if the app name is set in the config.
+
+    Args:
+        config: The config object.
+
+    Raises:
+        RuntimeError: If the app name is not set in the config.
+    """
+    if not config.app_name:
+        raise RuntimeError(
+            "Cannot get the app module because `app_name` is not set in rxconfig! "
+            "If this error occurs in a reflex test case, ensure that `get_app` is mocked."
+        )
+
+
 def get_app(reload: bool = False) -> ModuleType:
     """Get the app module based on the default config.
 
@@ -277,22 +290,23 @@ def get_app(reload: bool = False) -> ModuleType:
         The app based on the default config.
 
     Raises:
-        RuntimeError: If the app name is not set in the config.
+        Exception: If an error occurs while getting the app module.
     """
     from reflex.utils import telemetry
 
     try:
         environment.RELOAD_CONFIG.set(reload)
         config = get_config()
-        if not config.app_name:
-            raise RuntimeError(
-                "Cannot get the app module because `app_name` is not set in rxconfig! "
-                "If this error occurs in a reflex test case, ensure that `get_app` is mocked."
-            )
+
+        _check_app_name(config)
+
         module = config.module
         sys.path.insert(0, str(Path.cwd()))
-        app = __import__(module, fromlist=(constants.CompileVars.APP,))
-
+        app = (
+            __import__(module, fromlist=(constants.CompileVars.APP,))
+            if not config.app_module
+            else config.app_module
+        )
         if reload:
             from reflex.state import reload_state_module
 
@@ -301,11 +315,34 @@ def get_app(reload: bool = False) -> ModuleType:
 
             # Reload the app module.
             importlib.reload(app)
-
-        return app
     except Exception as ex:
         telemetry.send_error(ex, context="frontend")
         raise
+    else:
+        return app
+
+
+def get_and_validate_app(reload: bool = False) -> AppInfo:
+    """Get the app instance based on the default config and validate it.
+
+    Args:
+        reload: Re-import the app module from disk
+
+    Returns:
+        The app instance and the app module.
+
+    Raises:
+        RuntimeError: If the app instance is not an instance of rx.App.
+    """
+    from reflex.app import App
+
+    app_module = get_app(reload=reload)
+    app = getattr(app_module, constants.CompileVars.APP)
+    if not isinstance(app, App):
+        raise RuntimeError(
+            "The app instance in the specified app_module_import in rxconfig must be an instance of rx.App."
+        )
+    return AppInfo(app=app, module=app_module)
 
 
 def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
@@ -318,8 +355,7 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
     Returns:
         The compiled app based on the default config.
     """
-    app_module = get_app(reload=reload)
-    app = getattr(app_module, constants.CompileVars.APP)
+    app, app_module = get_and_validate_app(reload=reload)
     # For py3.9 compatibility when redis is used, we MUST add any decorator pages
     # before compiling the app in a thread to avoid event loop error (REF-2172).
     app._apply_decorated_pages()
@@ -835,7 +871,11 @@ def install_node():
 
 
 def install_bun():
-    """Install bun onto the user's system."""
+    """Install bun onto the user's system.
+
+    Raises:
+        SystemPackageMissingError: If "unzip" is missing.
+    """
     win_supported = is_windows_bun_supported()
     one_drive_in_path = windows_check_onedrive_in_path()
     if constants.IS_WINDOWS and (not win_supported or one_drive_in_path):
@@ -874,7 +914,7 @@ def install_bun():
     else:
         unzip_path = path_ops.which("unzip")
         if unzip_path is None:
-            raise_system_package_missing_error("unzip")
+            raise SystemPackageMissingError("unzip")
 
         # Run the bun install script.
         download_and_run(
@@ -1153,11 +1193,12 @@ def ensure_reflex_installation_id() -> Optional[int]:
         if installation_id is None:
             installation_id = random.getrandbits(128)
             installation_id_file.write_text(str(installation_id))
-        # If we get here, installation_id is definitely set
-        return installation_id
     except Exception as e:
         console.debug(f"Failed to ensure reflex installation id: {e}")
         return None
+    else:
+        # If we get here, installation_id is definitely set
+        return installation_id
 
 
 def initialize_reflex_user_directory():
@@ -1371,19 +1412,22 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
     except OSError as ose:
         console.error(f"Failed to create temp directory for extracting zip: {ose}")
         raise typer.Exit(1) from ose
+
     try:
         zipfile.ZipFile(zip_file_path).extractall(path=unzip_dir)
         # The zip file downloaded from github looks like:
         # repo-name-branch/**/*, so we need to remove the top level directory.
-        if len(subdirs := os.listdir(unzip_dir)) != 1:
-            console.error(f"Expected one directory in the zip, found {subdirs}")
-            raise typer.Exit(1)
-        template_dir = unzip_dir / subdirs[0]
-        console.debug(f"Template folder is located at {template_dir}")
     except Exception as uze:
         console.error(f"Failed to unzip the template: {uze}")
         raise typer.Exit(1) from uze
 
+    if len(subdirs := os.listdir(unzip_dir)) != 1:
+        console.error(f"Expected one directory in the zip, found {subdirs}")
+        raise typer.Exit(1)
+
+    template_dir = unzip_dir / subdirs[0]
+    console.debug(f"Template folder is located at {template_dir}")
+
     # Move the rxconfig file here first.
     path_ops.mv(str(template_dir / constants.Config.FILE), constants.Config.FILE)
     new_config = get_config(reload=True)

+ 1 - 2
reflex/utils/processes.py

@@ -9,7 +9,6 @@ import os
 import signal
 import subprocess
 from concurrent import futures
-from pathlib import Path
 from typing import Callable, Generator, List, Optional, Tuple, Union
 
 import psutil
@@ -368,7 +367,7 @@ def get_command_with_loglevel(command: list[str]) -> list[str]:
         The updated command list
     """
     npm_path = path_ops.get_npm_path()
-    npm_path = str(Path(npm_path).resolve()) if npm_path else npm_path
+    npm_path = str(npm_path) if npm_path else None
 
     if command[0] == npm_path:
         return [*command, "--loglevel", "silly"]

+ 3 - 2
reflex/utils/telemetry.py

@@ -122,7 +122,7 @@ def _prepare_event(event: str, **kwargs) -> dict:
         return {}
 
     if UTC is None:
-        # for python 3.9 & 3.10
+        # for python 3.10
         stamp = datetime.utcnow().isoformat()
     else:
         # for python 3.11 & 3.12
@@ -156,9 +156,10 @@ def _prepare_event(event: str, **kwargs) -> dict:
 def _send_event(event_data: dict) -> bool:
     try:
         httpx.post(POSTHOG_API_URL, json=event_data)
-        return True
     except Exception:
         return False
+    else:
+        return True
 
 
 def _send(event, telemetry_enabled, **kwargs):

+ 1 - 3
reflex/utils/types.py

@@ -855,9 +855,7 @@ def safe_issubclass(cls: Any, class_or_tuple: Any, /) -> bool:
     try:
         return issubclass(cls, class_or_tuple)
     except TypeError as e:
-        raise TypeError(
-            f"Invalid arguments for issubclass: {cls}, {class_or_tuple}"
-        ) from e
+        return False
 
 
 def infallible_issubclass(cls: Any, class_or_tuple: Any, /) -> bool:

+ 29 - 61
reflex/vars/base.py

@@ -26,6 +26,7 @@ from typing import (
     Iterable,
     List,
     Literal,
+    Mapping,
     NoReturn,
     Optional,
     Sequence,
@@ -53,6 +54,7 @@ from reflex.base import Base
 from reflex.constants.compiler import Hooks
 from reflex.utils import console, exceptions, imports, serializers, types
 from reflex.utils.exceptions import (
+    UntypedComputedVarError,
     VarAttributeError,
     VarDependencyError,
     VarTypeError,
@@ -74,6 +76,7 @@ from reflex.utils.types import (
     has_args,
     infallible_issubclass,
     typehint_issubclass,
+    safe_issubclass,
     unionize,
 )
 
@@ -223,7 +226,7 @@ class VarData:
         state: str = "",
         field_name: str = "",
         imports: ImportDict | ParsedImportDict | None = None,
-        hooks: dict[str, VarData | None] | None = None,
+        hooks: Mapping[str, VarData | None] | None = None,
         components: Iterable[BaseComponent] | None = None,
         deps: list[Var] | None = None,
         position: Hooks.HookPosition | None = None,
@@ -644,52 +647,21 @@ class Var(Generic[VAR_TYPE]):
     def create(
         cls,
         value: FAKE_VAR_TYPE,
-        _var_is_local: bool | None = None,
-        _var_is_string: bool | None = None,
         _var_data: VarData | None = None,
     ) -> Var[FAKE_VAR_TYPE]:
         """Create a var from a value.
 
         Args:
             value: The value to create the var from.
-            _var_is_local: Whether the var is local. Deprecated.
-            _var_is_string: Whether the var is a string literal. Deprecated.
             _var_data: Additional hooks and imports associated with the Var.
 
         Returns:
             The var.
         """
-        if _var_is_local is not None:
-            console.deprecate(
-                feature_name="_var_is_local",
-                reason="The _var_is_local argument is not supported for Var. "
-                "If you want to create a Var from a raw Javascript expression, use the constructor directly",
-                deprecation_version="0.6.0",
-                removal_version="0.7.0",
-            )
-        if _var_is_string is not None:
-            console.deprecate(
-                feature_name="_var_is_string",
-                reason="The _var_is_string argument is not supported for Var. "
-                "If you want to create a Var from a raw Javascript expression, use the constructor directly",
-                deprecation_version="0.6.0",
-                removal_version="0.7.0",
-            )
-
         # If the value is already a var, do nothing.
         if isinstance(value, Var):
             return value
 
-        # Try to pull the imports and hooks from contained values.
-        if not isinstance(value, str):
-            return LiteralVar.create(value, _var_data=_var_data)
-
-        if _var_is_string is False or _var_is_local is True:
-            return Var(
-                _js_expr=value,
-                _var_data=_var_data,
-            )
-
         return LiteralVar.create(value, _var_data=_var_data)
 
     @classmethod
@@ -747,8 +719,8 @@ class Var(Generic[VAR_TYPE]):
     @overload
     def to(
         self,
-        output: type[dict],
-    ) -> ObjectVar[dict]: ...
+        output: type[Mapping],
+    ) -> ObjectVar[Mapping]: ...
 
     @overload
     def to(
@@ -952,7 +924,7 @@ class Var(Generic[VAR_TYPE]):
             return False
         if issubclass(type_, list):
             return []
-        if issubclass(type_, dict):
+        if issubclass(type_, Mapping):
             return {}
         if issubclass(type_, tuple):
             return ()
@@ -1158,7 +1130,7 @@ class Var(Generic[VAR_TYPE]):
                     f"$/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")]
                 }
             ),
-        ).to(ObjectVar, Dict[str, str])
+        ).to(ObjectVar, Mapping[str, str])
         return refs[LiteralVar.create(str(self))]
 
     @deprecated("Use `.js_type()` instead.")
@@ -1551,7 +1523,7 @@ class LiteralVar(Var):
 
         serialized_value = serializers.serialize(value)
         if serialized_value is not None:
-            if isinstance(serialized_value, dict):
+            if isinstance(serialized_value, Mapping):
                 return LiteralObjectVar.create(
                     serialized_value,
                     _var_type=type(value),
@@ -1930,8 +1902,8 @@ def figure_out_type(value: Any) -> types.GenericType:
         return Set[unionize(*(figure_out_type(v) for v in value))]
     if isinstance(value, tuple):
         return Tuple[unionize(*(figure_out_type(v) for v in value)), ...]
-    if isinstance(value, dict):
-        return Dict[
+    if isinstance(value, Mapping):
+        return Mapping[
             unionize(*(figure_out_type(k) for k in value)),
             unionize(*(figure_out_type(v) for v in value.values())),
         ]
@@ -2156,19 +2128,14 @@ class ComputedVar(Var[RETURN_TYPE]):
 
         Raises:
             TypeError: If the computed var dependencies are not Var instances or var names.
+            UntypedComputedVarError: If the computed var is untyped.
         """
         hint = kwargs.pop("return_type", None) or get_type_hints(fget).get(
             "return", Any
         )
 
         if hint is Any:
-            console.deprecate(
-                "untyped-computed-var",
-                "ComputedVar should have a return type annotation.",
-                "0.6.5",
-                "0.7.0",
-            )
-
+            raise UntypedComputedVarError(var_name=fget.__name__)
         kwargs.setdefault("_js_expr", fget.__name__)
         kwargs.setdefault("_var_type", hint)
 
@@ -2241,6 +2208,7 @@ class ComputedVar(Var[RETURN_TYPE]):
             "_var_data": kwargs.pop(
                 "_var_data", VarData.merge(self._var_data, merge_var_data)
             ),
+            "return_type": kwargs.pop("return_type", self._var_type),
         }
 
         if kwargs:
@@ -2299,10 +2267,10 @@ class ComputedVar(Var[RETURN_TYPE]):
 
     @overload
     def __get__(
-        self: ComputedVar[dict[DICT_KEY, DICT_VAL]],
+        self: ComputedVar[Mapping[DICT_KEY, DICT_VAL]],
         instance: None,
         owner: Type,
-    ) -> ObjectVar[dict[DICT_KEY, DICT_VAL]]: ...
+    ) -> ObjectVar[Mapping[DICT_KEY, DICT_VAL]]: ...
 
     @overload
     def __get__(
@@ -2368,12 +2336,9 @@ class ComputedVar(Var[RETURN_TYPE]):
             value = getattr(instance, self._cache_attr)
 
         if not _isinstance(value, self._var_type):
-            console.deprecate(
-                "mismatched-computed-var-return",
-                f"Computed var {type(instance).__name__}.{self._js_expr} returned value of type {type(value)}, "
-                f"expected {self._var_type}. This might cause unexpected behavior.",
-                "0.6.5",
-                "0.7.0",
+            console.error(
+                f"Computed var '{type(instance).__name__}.{self._js_expr}' must return"
+                f" type '{self._var_type}', got '{type(value)}'."
             )
 
         return value
@@ -3229,11 +3194,14 @@ V = TypeVar("V")
 
 BASE_TYPE = TypeVar("BASE_TYPE", bound=Base)
 
+FIELD_TYPE = TypeVar("FIELD_TYPE")
+MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping)
+
 
-class Field(Generic[T]):
+class Field(Generic[FIELD_TYPE]):
     """Shadow class for Var to allow for type hinting in the IDE."""
 
-    def __set__(self, instance, value: T):
+    def __set__(self, instance, value: FIELD_TYPE):
         """Set the Var.
 
         Args:
@@ -3265,8 +3233,8 @@ class Field(Generic[T]):
 
     @overload
     def __get__(
-        self: Field[Dict[str, V]], instance: None, owner
-    ) -> ObjectVar[Dict[str, V]]: ...
+        self: Field[MAPPING_TYPE], instance: None, owner
+    ) -> ObjectVar[MAPPING_TYPE]: ...
 
     @overload
     def __get__(
@@ -3274,10 +3242,10 @@ class Field(Generic[T]):
     ) -> ObjectVar[BASE_TYPE]: ...
 
     @overload
-    def __get__(self, instance: None, owner) -> Var[T]: ...
+    def __get__(self, instance: None, owner) -> Var[FIELD_TYPE]: ...
 
     @overload
-    def __get__(self, instance, owner) -> T: ...
+    def __get__(self, instance, owner) -> FIELD_TYPE: ...
 
     def __get__(self, instance, owner):  # type: ignore
         """Get the Var.
@@ -3288,7 +3256,7 @@ class Field(Generic[T]):
         """
 
 
-def field(value: T) -> Field[T]:
+def field(value: FIELD_TYPE) -> Field[FIELD_TYPE]:
     """Create a Field with a value.
 
     Args:

+ 44 - 33
reflex/vars/object.py

@@ -8,8 +8,8 @@ import typing
 from inspect import isclass
 from typing import (
     Any,
-    Dict,
     List,
+    Mapping,
     NoReturn,
     Sequence,
     Tuple,
@@ -20,6 +20,8 @@ from typing import (
     overload,
 )
 
+from typing_extensions import is_typeddict
+
 from reflex.utils import types
 from reflex.utils.exceptions import VarAttributeError
 from reflex.utils.types import (
@@ -55,7 +57,7 @@ ARRAY_INNER_TYPE = TypeVar("ARRAY_INNER_TYPE")
 OTHER_KEY_TYPE = TypeVar("OTHER_KEY_TYPE")
 
 
-class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
+class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping):
     """Base class for immutable object vars."""
 
     def _key_type(self) -> Type:
@@ -68,7 +70,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
 
     @overload
     def _value_type(
-        self: ObjectVar[Dict[Any, VALUE_TYPE]],
+        self: ObjectVar[Mapping[Any, VALUE_TYPE]],
     ) -> Type[VALUE_TYPE]: ...
 
     @overload
@@ -83,7 +85,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
         fixed_type = get_origin(self._var_type) or self._var_type
         if not isclass(fixed_type):
             return Any
-        args = get_args(self._var_type) if issubclass(fixed_type, dict) else ()
+        args = get_args(self._var_type) if issubclass(fixed_type, Mapping) else ()
         return args[1] if args else Any
 
     def keys(self) -> ArrayVar[Sequence[str]]:
@@ -96,7 +98,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
 
     @overload
     def values(
-        self: ObjectVar[Dict[Any, VALUE_TYPE]],
+        self: ObjectVar[Mapping[Any, VALUE_TYPE]],
     ) -> ArrayVar[Sequence[VALUE_TYPE]]: ...
 
     @overload
@@ -112,7 +114,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
 
     @overload
     def entries(
-        self: ObjectVar[Dict[Any, VALUE_TYPE]],
+        self: ObjectVar[Mapping[Any, VALUE_TYPE]],
     ) -> ArrayVar[Sequence[Tuple[str, VALUE_TYPE]]]: ...
 
     @overload
@@ -142,44 +144,50 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
     # NoReturn is used here to catch when key value is Any
     @overload
     def __getitem__(
-        self: ObjectVar[Dict[Any, NoReturn]],
+        self: ObjectVar[Mapping[Any, NoReturn]],
         key: Var | Any,
     ) -> Var: ...
 
+    @overload
+    def __getitem__(
+        self: (ObjectVar[Mapping[Any, bool]]),
+        key: Var | Any,
+    ) -> BooleanVar: ...
+
     @overload
     def __getitem__(
         self: (
-            ObjectVar[Dict[Any, int]]
-            | ObjectVar[Dict[Any, float]]
-            | ObjectVar[Dict[Any, int | float]]
+            ObjectVar[Mapping[Any, int]]
+            | ObjectVar[Mapping[Any, float]]
+            | ObjectVar[Mapping[Any, int | float]]
         ),
         key: Var | Any,
     ) -> NumberVar: ...
 
     @overload
     def __getitem__(
-        self: ObjectVar[Dict[Any, str]],
+        self: ObjectVar[Mapping[Any, str]],
         key: Var | Any,
     ) -> StringVar: ...
 
     @overload
     def __getitem__(
-        self: ObjectVar[Dict[Any, Sequence[ARRAY_INNER_TYPE]]]
-        | ObjectVar[Dict[Any, List[ARRAY_INNER_TYPE]]],
+        self: ObjectVar[Mapping[Any, Sequence[ARRAY_INNER_TYPE]]]
+        | ObjectVar[Mapping[Any, List[ARRAY_INNER_TYPE]]],
         key: Var | Any,
     ) -> ArrayVar[Sequence[ARRAY_INNER_TYPE]]: ...
 
     @overload
     def __getitem__(
-        self: ObjectVar[Dict[Any, set[ARRAY_INNER_TYPE]]],
+        self: ObjectVar[Mapping[Any, set[ARRAY_INNER_TYPE]]],
         key: Var | Any,
     ) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ...
 
     @overload
     def __getitem__(
-        self: ObjectVar[Dict[Any, dict[OTHER_KEY_TYPE, VALUE_TYPE]]],
+        self: ObjectVar[Mapping[Any, Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]],
         key: Var | Any,
-    ) -> ObjectVar[dict[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
+    ) -> ObjectVar[Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
 
     def __getitem__(self, key: Var | Any) -> Var:
         """Get an item from the object.
@@ -199,43 +207,43 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
     # NoReturn is used here to catch when key value is Any
     @overload
     def __getattr__(  # pyright: ignore [reportOverlappingOverload]
-        self: ObjectVar[Dict[Any, NoReturn]],
+        self: ObjectVar[Mapping[Any, NoReturn]],
         name: str,
     ) -> Var: ...
 
     @overload
     def __getattr__(
         self: (
-            ObjectVar[Dict[Any, int]]
-            | ObjectVar[Dict[Any, float]]
-            | ObjectVar[Dict[Any, int | float]]
+            ObjectVar[Mapping[Any, int]]
+            | ObjectVar[Mapping[Any, float]]
+            | ObjectVar[Mapping[Any, int | float]]
         ),
         name: str,
     ) -> NumberVar: ...
 
     @overload
     def __getattr__(
-        self: ObjectVar[Dict[Any, str]],
+        self: ObjectVar[Mapping[Any, str]],
         name: str,
     ) -> StringVar: ...
 
     @overload
     def __getattr__(
-        self: ObjectVar[Dict[Any, Sequence[ARRAY_INNER_TYPE]]],
+        self: ObjectVar[Mapping[Any, Sequence[ARRAY_INNER_TYPE]]],
         name: str,
     ) -> ArrayVar[Sequence[ARRAY_INNER_TYPE]]: ...
 
     @overload
     def __getattr__(
-        self: ObjectVar[Dict[Any, set[ARRAY_INNER_TYPE]]],
+        self: ObjectVar[Mapping[Any, set[ARRAY_INNER_TYPE]]],
         name: str,
     ) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ...
 
     @overload
     def __getattr__(
-        self: ObjectVar[Dict[Any, dict[OTHER_KEY_TYPE, VALUE_TYPE]]],
+        self: ObjectVar[Mapping[Any, Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]],
         name: str,
-    ) -> ObjectVar[dict[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
+    ) -> ObjectVar[Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
 
     @overload
     def __getattr__(
@@ -264,8 +272,11 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
             var_type = get_args(var_type)[0]
 
         fixed_type = var_type if isclass(var_type) else get_origin(var_type)
-        if (isclass(fixed_type) and not issubclass(fixed_type, dict)) or (
-            fixed_type in types.UnionTypes
+
+        if (
+            (isclass(fixed_type) and not issubclass(fixed_type, Mapping))
+            or (fixed_type in types.UnionTypes)
+            or is_typeddict(fixed_type)
         ):
             attribute_type = get_attribute_access_type(var_type, name)
             if attribute_type is None:
@@ -297,7 +308,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
 class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar):
     """Base class for immutable literal object vars."""
 
-    _var_value: Dict[Union[Var, Any], Union[Var, Any]] = dataclasses.field(
+    _var_value: Mapping[Union[Var, Any], Union[Var, Any]] = dataclasses.field(
         default_factory=dict
     )
 
@@ -381,7 +392,7 @@ class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar):
     @classmethod
     def create(
         cls,
-        _var_value: dict,
+        _var_value: Mapping,
         _var_type: Type[OBJECT_TYPE] | None = None,
         _var_data: VarData | None = None,
     ) -> LiteralObjectVar[OBJECT_TYPE]:
@@ -477,14 +488,14 @@ def object_merge_operation(lhs: Var, rhs: Var):
     return var_operation_return(
         js_expression=f"({{...{lhs}, ...{rhs}}})",
         type_computer=nary_type_computer(
-            ReflexCallable[[Any, Any], Dict[Any, Any]],
-            ReflexCallable[[Any], Dict[Any, Any]],
-            computer=lambda args: Dict[
+            ReflexCallable[[Any, Any], Mapping[Any, Any]],
+            ReflexCallable[[Any], Mapping[Any, Any]],
+            computer=lambda args: Mapping[
                 unionize(*[arg.to(ObjectVar)._key_type() for arg in args]),
                 unionize(*[arg.to(ObjectVar)._value_type() for arg in args]),
             ],
         ),
-        var_type=Dict[Any, Any],
+        var_type=Mapping[Any, Any],
     )
 
 

+ 1 - 1
tests/integration/init-test/Dockerfile

@@ -1,4 +1,4 @@
-FROM python:3.9
+FROM python:3.10
 
 ARG USERNAME=kerrigan
 RUN useradd -m $USERNAME

+ 25 - 23
tests/integration/test_call_script.py

@@ -16,7 +16,7 @@ from .utils import SessionStorage
 def CallScript():
     """A test app for browser javascript integration."""
     from pathlib import Path
-    from typing import Dict, List, Optional, Union
+    from typing import Optional, Union
 
     import reflex as rx
 
@@ -43,15 +43,17 @@ def CallScript():
     external_scripts = inline_scripts.replace("inline", "external")
 
     class CallScriptState(rx.State):
-        results: List[Optional[Union[str, Dict, List]]] = []
-        inline_counter: int = 0
-        external_counter: int = 0
+        results: rx.Field[list[Optional[Union[str, dict, list]]]] = rx.field([])
+        inline_counter: rx.Field[int] = rx.field(0)
+        external_counter: rx.Field[int] = rx.field(0)
         value: str = "Initial"
-        last_result: str = ""
+        last_result: int = 0
 
+        @rx.event
         def call_script_callback(self, result):
             self.results.append(result)
 
+        @rx.event
         def call_script_callback_other_arg(self, result, other_arg):
             self.results.append([other_arg, result])
 
@@ -91,7 +93,7 @@ def CallScript():
         def call_script_inline_return_lambda(self):
             return rx.call_script(
                 "inline2()",
-                callback=lambda result: CallScriptState.call_script_callback_other_arg(  # type: ignore
+                callback=lambda result: CallScriptState.call_script_callback_other_arg(
                     result, "lambda"
                 ),
             )
@@ -100,7 +102,7 @@ def CallScript():
         def get_inline_counter(self):
             return rx.call_script(
                 "inline_counter",
-                callback=CallScriptState.set_inline_counter,  # type: ignore
+                callback=CallScriptState.setvar("inline_counter"),
             )
 
         @rx.event
@@ -139,7 +141,7 @@ def CallScript():
         def call_script_external_return_lambda(self):
             return rx.call_script(
                 "external2()",
-                callback=lambda result: CallScriptState.call_script_callback_other_arg(  # type: ignore
+                callback=lambda result: CallScriptState.call_script_callback_other_arg(
                     result, "lambda"
                 ),
             )
@@ -148,28 +150,28 @@ def CallScript():
         def get_external_counter(self):
             return rx.call_script(
                 "external_counter",
-                callback=CallScriptState.set_external_counter,  # type: ignore
+                callback=CallScriptState.setvar("external_counter"),
             )
 
         @rx.event
         def call_with_var_f_string(self):
             return rx.call_script(
                 f"{rx.Var('inline_counter')} + {rx.Var('external_counter')}",
-                callback=CallScriptState.set_last_result,  # type: ignore
+                callback=CallScriptState.setvar("last_result"),
             )
 
         @rx.event
         def call_with_var_str_cast(self):
             return rx.call_script(
                 f"{rx.Var('inline_counter')!s} + {rx.Var('external_counter')!s}",
-                callback=CallScriptState.set_last_result,  # type: ignore
+                callback=CallScriptState.setvar("last_result"),
             )
 
         @rx.event
         def call_with_var_f_string_wrapped(self):
             return rx.call_script(
                 rx.Var(f"{rx.Var('inline_counter')} + {rx.Var('external_counter')}"),
-                callback=CallScriptState.set_last_result,  # type: ignore
+                callback=CallScriptState.setvar("last_result"),
             )
 
         @rx.event
@@ -178,7 +180,7 @@ def CallScript():
                 rx.Var(
                     f"{rx.Var('inline_counter')!s} + {rx.Var('external_counter')!s}"
                 ),
-                callback=CallScriptState.set_last_result,  # type: ignore
+                callback=CallScriptState.setvar("last_result"),
             )
 
         @rx.event
@@ -193,17 +195,17 @@ def CallScript():
     def index():
         return rx.vstack(
             rx.input(
-                value=CallScriptState.inline_counter.to(str),  # type: ignore
+                value=CallScriptState.inline_counter.to(str),
                 id="inline_counter",
                 read_only=True,
             ),
             rx.input(
-                value=CallScriptState.external_counter.to(str),  # type: ignore
+                value=CallScriptState.external_counter.to(str),
                 id="external_counter",
                 read_only=True,
             ),
             rx.text_area(
-                value=CallScriptState.results.to_string(),  # type: ignore
+                value=CallScriptState.results.to_string(),
                 id="results",
                 read_only=True,
             ),
@@ -273,7 +275,7 @@ def CallScript():
                 CallScriptState.value,
                 on_click=rx.call_script(
                     "'updated'",
-                    callback=CallScriptState.set_value,  # type: ignore
+                    callback=CallScriptState.setvar("value"),
                 ),
                 id="update_value",
             ),
@@ -282,7 +284,7 @@ def CallScript():
                 value=CallScriptState.last_result,
                 id="last_result",
                 read_only=True,
-                on_click=CallScriptState.set_last_result(""),  # type: ignore
+                on_click=CallScriptState.setvar("last_result", 0),
             ),
             rx.button(
                 "call_with_var_f_string",
@@ -308,7 +310,7 @@ def CallScript():
                 "call_with_var_f_string_inline",
                 on_click=rx.call_script(
                     f"{rx.Var('inline_counter')} + {CallScriptState.last_result}",
-                    callback=CallScriptState.set_last_result,  # type: ignore
+                    callback=CallScriptState.setvar("last_result"),
                 ),
                 id="call_with_var_f_string_inline",
             ),
@@ -316,7 +318,7 @@ def CallScript():
                 "call_with_var_str_cast_inline",
                 on_click=rx.call_script(
                     f"{rx.Var('inline_counter')!s} + {rx.Var('external_counter')!s}",
-                    callback=CallScriptState.set_last_result,  # type: ignore
+                    callback=CallScriptState.setvar("last_result"),
                 ),
                 id="call_with_var_str_cast_inline",
             ),
@@ -326,7 +328,7 @@ def CallScript():
                     rx.Var(
                         f"{rx.Var('inline_counter')} + {CallScriptState.last_result}"
                     ),
-                    callback=CallScriptState.set_last_result,  # type: ignore
+                    callback=CallScriptState.setvar("last_result"),
                 ),
                 id="call_with_var_f_string_wrapped_inline",
             ),
@@ -336,7 +338,7 @@ def CallScript():
                     rx.Var(
                         f"{rx.Var('inline_counter')!s} + {rx.Var('external_counter')!s}"
                     ),
-                    callback=CallScriptState.set_last_result,  # type: ignore
+                    callback=CallScriptState.setvar("last_result"),
                 ),
                 id="call_with_var_str_cast_wrapped_inline",
             ),
@@ -483,7 +485,7 @@ def test_call_script_w_var(
     """
     assert_token(driver)
     last_result = driver.find_element(By.ID, "last_result")
-    assert last_result.get_attribute("value") == ""
+    assert last_result.get_attribute("value") == "0"
 
     inline_return_button = driver.find_element(By.ID, "inline_return")
 

+ 6 - 6
tests/integration/test_client_storage.py

@@ -33,18 +33,18 @@ def ClientSide():
     class ClientSideSubState(ClientSideState):
         # cookies with default settings
         c1: str = rx.Cookie()
-        c2: rx.Cookie = "c2 default"  # type: ignore
+        c2: str = rx.Cookie("c2 default")
 
         # cookies with custom settings
         c3: str = rx.Cookie(max_age=2)  # expires after 2 second
-        c4: rx.Cookie = rx.Cookie(same_site="strict")
+        c4: str = rx.Cookie(same_site="strict")
         c5: str = rx.Cookie(path="/foo/")  # only accessible on `/foo/`
         c6: str = rx.Cookie(name="c6")
         c7: str = rx.Cookie("c7 default")
 
         # local storage with default settings
         l1: str = rx.LocalStorage()
-        l2: rx.LocalStorage = "l2 default"  # type: ignore
+        l2: str = rx.LocalStorage("l2 default")
 
         # local storage with custom settings
         l3: str = rx.LocalStorage(name="l3")
@@ -56,7 +56,7 @@ def ClientSide():
 
         # Session storage
         s1: str = rx.SessionStorage()
-        s2: rx.SessionStorage = "s2 default"  # type: ignore
+        s2: str = rx.SessionStorage("s2 default")
         s3: str = rx.SessionStorage(name="s3")
 
         def set_l6(self, my_param: str):
@@ -87,13 +87,13 @@ def ClientSide():
             rx.input(
                 placeholder="state var",
                 value=ClientSideState.state_var,
-                on_change=ClientSideState.set_state_var,  # type: ignore
+                on_change=ClientSideState.setvar("state_var"),
                 id="state_var",
             ),
             rx.input(
                 placeholder="input value",
                 value=ClientSideState.input_value,
-                on_change=ClientSideState.set_input_value,  # type: ignore
+                on_change=ClientSideState.setvar("input_value"),
                 id="input_value",
             ),
             rx.button(

+ 2 - 1
tests/integration/test_connection_banner.py

@@ -71,9 +71,10 @@ def has_error_modal(driver: WebDriver) -> bool:
     """
     try:
         driver.find_element(By.XPATH, CONNECTION_ERROR_XPATH)
-        return True
     except NoSuchElementException:
         return False
+    else:
+        return True
 
 
 @pytest.mark.asyncio

+ 0 - 13
tests/units/assets/test_assets.py

@@ -37,19 +37,6 @@ def test_shared_asset() -> None:
     assert not Path(Path.cwd() / "assets/external").exists()
 
 
-def test_deprecated_x_asset(capsys) -> None:
-    """Test that the deprecated asset function raises a warning.
-
-    Args:
-        capsys: Pytest fixture that captures stdout and stderr.
-    """
-    assert rx.asset("custom_script.js", shared=True) == rx._x.asset("custom_script.js")
-    assert (
-        "DeprecationWarning: rx._x.asset has been deprecated in version 0.6.6"
-        in capsys.readouterr().out
-    )
-
-
 @pytest.mark.parametrize(
     "path,shared",
     [

+ 6 - 8
tests/units/components/datadisplay/conftest.py

@@ -1,7 +1,5 @@
 """Data display component tests fixtures."""
 
-from typing import List
-
 import pandas as pd
 import pytest
 
@@ -54,11 +52,11 @@ def data_table_state3():
     """
 
     class DataTableState(BaseState):
-        _data: List = []
-        _columns: List = ["col1", "col2"]
+        _data: list = []
+        _columns: list = ["col1", "col2"]
 
         @rx.var
-        def data(self) -> List:
+        def data(self) -> list:
             return self._data
 
         @rx.var
@@ -77,15 +75,15 @@ def data_table_state4():
     """
 
     class DataTableState(BaseState):
-        _data: List = []
-        _columns: List = ["col1", "col2"]
+        _data: list = []
+        _columns: list[str] = ["col1", "col2"]
 
         @rx.var
         def data(self):
             return self._data
 
         @rx.var
-        def columns(self) -> List:
+        def columns(self) -> list:
             return self._columns
 
     return DataTableState

+ 5 - 4
tests/units/components/datadisplay/test_datatable.py

@@ -4,6 +4,7 @@ import pytest
 import reflex as rx
 from reflex.components.gridjs.datatable import DataTable
 from reflex.utils import types
+from reflex.utils.exceptions import UntypedComputedVarError
 from reflex.utils.serializers import serialize, serialize_dataframe
 
 
@@ -76,17 +77,17 @@ def test_invalid_props(props):
     [
         (
             "data_table_state2",
-            "Annotation of the computed var assigned to the data field should be provided.",
+            "Computed var 'data' must have a type annotation.",
             True,
         ),
         (
             "data_table_state3",
-            "Annotation of the computed var assigned to the column field should be provided.",
+            "Computed var 'columns' must have a type annotation.",
             False,
         ),
         (
             "data_table_state4",
-            "Annotation of the computed var assigned to the data field should be provided.",
+            "Computed var 'data' must have a type annotation.",
             False,
         ),
     ],
@@ -100,7 +101,7 @@ def test_computed_var_without_annotation(fixture, request, err_msg, is_data_fram
         err_msg: expected error message.
         is_data_frame: whether data field is a pandas dataframe.
     """
-    with pytest.raises(ValueError) as err:
+    with pytest.raises(UntypedComputedVarError) as err:
         if is_data_frame:
             DataTable.create(data=request.getfixturevalue(fixture).data)
         else:

+ 49 - 45
tests/units/components/test_component.py

@@ -19,6 +19,7 @@ from reflex.constants import EventTriggers
 from reflex.event import (
     EventChain,
     EventHandler,
+    JavascriptInputEvent,
     input_event,
     no_args_event_spec,
     parse_args_spec,
@@ -27,7 +28,11 @@ from reflex.event import (
 from reflex.state import BaseState
 from reflex.style import Style
 from reflex.utils import imports
-from reflex.utils.exceptions import EventFnArgMismatch
+from reflex.utils.exceptions import (
+    ChildrenTypeError,
+    EventFnArgMismatchError,
+    EventHandlerArgTypeMismatchError,
+)
 from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports
 from reflex.vars import VarData
 from reflex.vars.base import LiteralVar, Var
@@ -94,11 +99,14 @@ def component2() -> Type[Component]:
         A test component.
     """
 
+    def on_prop_event_spec(e0: Any):
+        return [e0]
+
     class TestComponent2(Component):
         # A test list prop.
         arr: Var[List[str]]
 
-        on_prop_event: EventHandler[lambda e0: [e0]]
+        on_prop_event: EventHandler[on_prop_event_spec]
 
         def get_event_triggers(self) -> Dict[str, Any]:
             """Test controlled triggers.
@@ -645,14 +653,17 @@ def test_create_filters_none_props(test_component):
     assert str(component.style["text-align"]) == '"center"'
 
 
-@pytest.mark.parametrize("children", [((None,),), ("foo", ("bar", (None,)))])
+@pytest.mark.parametrize(
+    "children",
+    [
+        ((None,),),
+        ("foo", ("bar", (None,))),
+        ({"foo": "bar"},),
+    ],
+)
 def test_component_create_unallowed_types(children, test_component):
-    with pytest.raises(TypeError) as err:
+    with pytest.raises(ChildrenTypeError):
         test_component.create(*children)
-    assert (
-        err.value.args[0]
-        == "Children of Reflex components must be other components, state vars, or primitive Python types. Got child None of type <class 'NoneType'>."
-    )
 
 
 @pytest.mark.parametrize(
@@ -815,10 +826,14 @@ def test_component_create_unpack_tuple_child(test_component, element, expected):
     assert fragment_wrapper.render() == expected
 
 
+class _Obj(Base):
+    custom: int = 0
+
+
 class C1State(BaseState):
     """State for testing C1 component."""
 
-    def mock_handler(self, _e, _bravo, _charlie):
+    def mock_handler(self, _e: JavascriptInputEvent, _bravo: dict, _charlie: _Obj):
         """Mock handler."""
         pass
 
@@ -826,10 +841,12 @@ class C1State(BaseState):
 def test_component_event_trigger_arbitrary_args():
     """Test that we can define arbitrary types for the args of an event trigger."""
 
-    class Obj(Base):
-        custom: int = 0
-
-    def on_foo_spec(_e, alpha: str, bravo: Dict[str, Any], charlie: Obj):
+    def on_foo_spec(
+        _e: Var[JavascriptInputEvent],
+        alpha: Var[str],
+        bravo: dict[str, Any],
+        charlie: Var[_Obj],
+    ):
         return [_e.target.value, bravo["nested"], charlie.custom + 42]
 
     class C1(Component):
@@ -842,13 +859,7 @@ def test_component_event_trigger_arbitrary_args():
                 "on_foo": on_foo_spec,
             }
 
-    comp = C1.create(on_foo=C1State.mock_handler)
-
-    assert comp.render()["props"][0] == (
-        "onFoo={((__e, _alpha, _bravo, _charlie) => (addEvents("
-        f'[(Event("{C1State.get_full_name()}.mock_handler", ({{ ["_e"] : __e["target"]["value"], ["_bravo"] : _bravo["nested"], ["_charlie"] : (((_lhs, _rhs) => (_lhs + _rhs))(_charlie["custom"], 42)) }}), ({{  }})))], '
-        "[__e, _alpha, _bravo, _charlie], ({  }))))}"
-    )
+    C1.create(on_foo=C1State.mock_handler)
 
 
 def test_create_custom_component(my_component):
@@ -905,30 +916,29 @@ def test_invalid_event_handler_args(component2, test_state):
         test_state: A test state.
     """
     # EventHandler args must match
-    with pytest.raises(EventFnArgMismatch):
+    with pytest.raises(EventFnArgMismatchError):
         component2.create(on_click=test_state.do_something_arg)
 
     # Multiple EventHandler args: all must match
-    with pytest.raises(EventFnArgMismatch):
+    with pytest.raises(EventFnArgMismatchError):
         component2.create(
             on_click=[test_state.do_something_arg, test_state.do_something]
         )
 
-    # Enable when 0.7.0 happens
     # # Event Handler types must match
-    # with pytest.raises(EventHandlerArgTypeMismatch):
-    #     component2.create(
-    #         on_user_visited_count_changed=test_state.do_something_with_bool # noqa: ERA001 RUF100
-    #     ) # noqa: ERA001 RUF100
-    # with pytest.raises(EventHandlerArgTypeMismatch):
-    #     component2.create(on_user_list_changed=test_state.do_something_with_int) #noqa: ERA001
-    # with pytest.raises(EventHandlerArgTypeMismatch):
-    #     component2.create(on_user_list_changed=test_state.do_something_with_list_int) #noqa: ERA001
-
-    # component2.create(on_open=test_state.do_something_with_int) #noqa: ERA001
-    # component2.create(on_open=test_state.do_something_with_bool) #noqa: ERA001
-    # component2.create(on_user_visited_count_changed=test_state.do_something_with_int) #noqa: ERA001
-    # component2.create(on_user_list_changed=test_state.do_something_with_list_str) #noqa: ERA001
+    with pytest.raises(EventHandlerArgTypeMismatchError):
+        component2.create(
+            on_user_visited_count_changed=test_state.do_something_with_bool
+        )
+    with pytest.raises(EventHandlerArgTypeMismatchError):
+        component2.create(on_user_list_changed=test_state.do_something_with_int)
+    with pytest.raises(EventHandlerArgTypeMismatchError):
+        component2.create(on_user_list_changed=test_state.do_something_with_list_int)
+
+    component2.create(on_open=test_state.do_something_with_int)
+    component2.create(on_open=test_state.do_something_with_bool)
+    component2.create(on_user_visited_count_changed=test_state.do_something_with_int)
+    component2.create(on_user_list_changed=test_state.do_something_with_list_str)
 
     # lambda cannot return weird values.
     with pytest.raises(ValueError):
@@ -941,15 +951,15 @@ def test_invalid_event_handler_args(component2, test_state):
         )
 
     # lambda signature must match event trigger.
-    with pytest.raises(EventFnArgMismatch):
+    with pytest.raises(EventFnArgMismatchError):
         component2.create(on_click=lambda _: test_state.do_something_arg(1))
 
     # lambda returning EventHandler must match spec
-    with pytest.raises(EventFnArgMismatch):
+    with pytest.raises(EventFnArgMismatchError):
         component2.create(on_click=lambda: test_state.do_something_arg)
 
     # Mixed EventSpec and EventHandler must match spec.
-    with pytest.raises(EventFnArgMismatch):
+    with pytest.raises(EventFnArgMismatchError):
         component2.create(
             on_click=lambda: [
                 test_state.do_something_arg(1),
@@ -1795,21 +1805,15 @@ def test_custom_component_declare_event_handlers_in_fields():
             """
             return {
                 **super().get_event_triggers(),
-                "on_a": lambda e0: [e0],
                 "on_b": input_event,
-                "on_c": lambda e0: [],
                 "on_d": lambda: [],
                 "on_e": lambda: [],
-                "on_f": lambda a, b, c: [c, b, a],
             }
 
     class TestComponent(Component):
-        on_a: EventHandler[lambda e0: [e0]]
         on_b: EventHandler[input_event]
-        on_c: EventHandler[no_args_event_spec]
         on_d: EventHandler[no_args_event_spec]
         on_e: EventHandler
-        on_f: EventHandler[lambda a, b, c: [c, b, a]]
 
     custom_component = ReferenceComponent.create()
     test_component = TestComponent.create()

+ 3 - 4
tests/units/test_event.py

@@ -200,16 +200,15 @@ def test_event_redirect(input, output):
         input: The input for running the test.
         output: The expected output to validate the test.
     """
-    path, external, replace = input
+    path, is_external, replace = input
     kwargs = {}
-    if external is not None:
-        kwargs["external"] = external
+    if is_external is not None:
+        kwargs["is_external"] = is_external
     if replace is not None:
         kwargs["replace"] = replace
     spec = event.redirect(path, **kwargs)
     assert isinstance(spec, EventSpec)
     assert spec.handler.fn.__qualname__ == "_redirect"
-
     assert format.format_event(spec) == output
 
 

+ 5 - 5
tests/units/test_state.py

@@ -1148,7 +1148,7 @@ def test_child_state():
 
     class ChildState(MainState):
         @computed_var
-        def rendered_var(self):
+        def rendered_var(self) -> int:
             return self.v
 
     ms = MainState()
@@ -1426,7 +1426,7 @@ def test_computed_var_dependencies():
             return self.testprop
 
         @rx.var
-        def comp_w(self):
+        def comp_w(self) -> Callable[[], int]:
             """Nested lambda.
 
             Returns:
@@ -1435,7 +1435,7 @@ def test_computed_var_dependencies():
             return lambda: self.w
 
         @rx.var
-        def comp_x(self):
+        def comp_x(self) -> Callable[[], int]:
             """Nested function.
 
             Returns:
@@ -1448,7 +1448,7 @@ def test_computed_var_dependencies():
             return _
 
         @rx.var
-        def comp_y(self) -> List[int]:
+        def comp_y(self) -> list[int]:
             """Comprehension iterating over attribute.
 
             Returns:
@@ -3140,7 +3140,7 @@ async def test_get_state_from_sibling_not_cached(mock_app: rx.App, token: str):
         child3_var: int = 0
 
         @rx.var(cache=False)
-        def v(self):
+        def v(self) -> None:
             pass
 
     class Grandchild3(Child3):

+ 2 - 2
tests/units/test_style.py

@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from typing import Any, Dict
+from typing import Any, Mapping
 
 import pytest
 
@@ -379,7 +379,7 @@ class StyleState(rx.State):
             {
                 "css": Var(
                     _js_expr=f'({{ ["color"] : ("dark"+{StyleState.color}) }})'
-                ).to(Dict[str, str])
+                ).to(Mapping[str, str])
             },
         ),
         (

+ 7 - 4
tests/units/test_var.py

@@ -2,7 +2,7 @@ import json
 import math
 import sys
 import typing
-from typing import Dict, List, Optional, Set, Tuple, Union, cast
+from typing import Dict, List, Mapping, Optional, Set, Tuple, Union, cast
 
 import pytest
 from pandas import DataFrame
@@ -11,7 +11,10 @@ import reflex as rx
 from reflex.base import Base
 from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
 from reflex.state import BaseState
-from reflex.utils.exceptions import PrimitiveUnserializableToJSON
+from reflex.utils.exceptions import (
+    PrimitiveUnserializableToJSON,
+    UntypedComputedVarError,
+)
 from reflex.utils.imports import ImportVar
 from reflex.vars import VarData
 from reflex.vars.base import (
@@ -270,7 +273,7 @@ def test_get_setter(prop: Var, expected):
         ([1, 2, 3], Var(_js_expr="[1, 2, 3]", _var_type=List[int])),
         (
             {"a": 1, "b": 2},
-            Var(_js_expr='({ ["a"] : 1, ["b"] : 2 })', _var_type=Dict[str, int]),
+            Var(_js_expr='({ ["a"] : 1, ["b"] : 2 })', _var_type=Mapping[str, int]),
         ),
     ],
 )
@@ -943,7 +946,7 @@ def test_shadow_computed_var_error(request: pytest.FixtureRequest, fixture: str)
         request: Fixture Request.
         fixture: The state fixture.
     """
-    with pytest.raises(NameError):
+    with pytest.raises(UntypedComputedVarError):
         state = request.getfixturevalue(fixture)
         state.var_without_annotation.foo
 

+ 5 - 5
tests/units/vars/test_base.py

@@ -1,4 +1,4 @@
-from typing import Dict, List, Union
+from typing import List, Mapping, Union
 
 import pytest
 
@@ -37,12 +37,12 @@ class ChildGenericDict(GenericDict):
         ("a", str),
         ([1, 2, 3], List[int]),
         ([1, 2.0, "a"], List[Union[int, float, str]]),
-        ({"a": 1, "b": 2}, Dict[str, int]),
-        ({"a": 1, 2: "b"}, Dict[Union[int, str], Union[str, int]]),
+        ({"a": 1, "b": 2}, Mapping[str, int]),
+        ({"a": 1, 2: "b"}, Mapping[Union[int, str], Union[str, int]]),
         (CustomDict(), CustomDict),
         (ChildCustomDict(), ChildCustomDict),
-        (GenericDict({1: 1}), Dict[int, int]),
-        (ChildGenericDict({1: 1}), Dict[int, int]),
+        (GenericDict({1: 1}), Mapping[int, int]),
+        (ChildGenericDict({1: 1}), Mapping[int, int]),
     ],
 )
 def test_figure_out_type(value, expected):