瀏覽代碼

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

Khaleel Al-Adhami 3 月之前
父節點
當前提交
24f341d125
共有 79 個文件被更改,包括 765 次插入397 次删除
  1. 13 5
      .github/workflows/benchmarks.yml
  2. 2 2
      .github/workflows/check_outdated_dependencies.yml
  3. 0 7
      .github/workflows/integration_app_harness.yml
  4. 1 1
      benchmarks/benchmark_package_size.py
  5. 3 3
      benchmarks/test_benchmark_compile_components.py
  6. 5 5
      benchmarks/test_benchmark_compile_pages.py
  7. 0 2
      poetry.lock
  8. 6 3
      pyproject.toml
  9. 1 4
      reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js
  10. 94 59
      reflex/app.py
  11. 3 3
      reflex/app_mixins/lifespan.py
  12. 0 3
      reflex/app_module_for_backend.py
  13. 1 1
      reflex/components/core/html.py
  14. 1 1
      reflex/components/core/upload.py
  15. 1 1
      reflex/components/datadisplay/code.py
  16. 5 3
      reflex/components/dynamic.py
  17. 13 3
      reflex/components/next/image.py
  18. 4 2
      reflex/components/next/image.pyi
  19. 1 1
      reflex/components/plotly/plotly.py
  20. 4 4
      reflex/components/radix/primitives/accordion.py
  21. 1 1
      reflex/components/radix/primitives/drawer.py
  22. 4 4
      reflex/components/radix/primitives/drawer.pyi
  23. 2 2
      reflex/components/radix/themes/components/icon_button.py
  24. 1 0
      reflex/components/radix/themes/components/icon_button.pyi
  25. 3 1
      reflex/components/radix/themes/components/tooltip.py
  26. 1 0
      reflex/components/radix/themes/components/tooltip.pyi
  27. 2 2
      reflex/components/recharts/general.py
  28. 2 2
      reflex/components/recharts/general.pyi
  29. 1 1
      reflex/components/recharts/polar.py
  30. 4 4
      reflex/components/recharts/recharts.py
  31. 1 1
      reflex/components/sonner/toast.py
  32. 4 7
      reflex/config.py
  33. 2 0
      reflex/constants/compiler.py
  34. 9 9
      reflex/constants/installer.py
  35. 1 1
      reflex/constants/style.py
  36. 4 4
      reflex/event.py
  37. 51 23
      reflex/experimental/client_state.py
  38. 4 4
      reflex/experimental/hooks.py
  39. 1 1
      reflex/experimental/layout.pyi
  40. 14 0
      reflex/reflex.py
  41. 23 24
      reflex/state.py
  42. 6 6
      reflex/testing.py
  43. 1 4
      reflex/utils/codespaces.py
  44. 10 10
      reflex/utils/exceptions.py
  45. 4 4
      reflex/utils/exec.py
  46. 166 3
      reflex/utils/prerequisites.py
  47. 1 1
      reflex/vars/base.py
  48. 2 2
      reflex/vars/function.py
  49. 3 3
      reflex/vars/number.py
  50. 20 9
      scripts/bun_install.sh
  51. 4 0
      scripts/install.ps1
  52. 0 30
      tests/integration/conftest.py
  53. 2 2
      tests/integration/test_background_task.py
  54. 1 1
      tests/integration/test_call_script.py
  55. 3 1
      tests/integration/test_client_storage.py
  56. 1 1
      tests/integration/test_component_state.py
  57. 1 1
      tests/integration/test_connection_banner.py
  58. 1 1
      tests/integration/test_deploy_url.py
  59. 1 1
      tests/integration/test_dynamic_routes.py
  60. 1 1
      tests/integration/test_event_actions.py
  61. 1 1
      tests/integration/test_event_chain.py
  62. 1 1
      tests/integration/test_exception_handlers.py
  63. 2 2
      tests/integration/test_form_submit.py
  64. 1 1
      tests/integration/test_input.py
  65. 1 1
      tests/integration/test_login_flow.py
  66. 1 1
      tests/integration/test_server_side_event.py
  67. 1 1
      tests/integration/test_upload.py
  68. 1 1
      tests/integration/test_var_operations.py
  69. 1 1
      tests/integration/tests_playwright/test_datetime_operations.py
  70. 1 1
      tests/integration/tests_playwright/test_table.py
  71. 1 30
      tests/units/conftest.py
  72. 1 1
      tests/units/middleware/test_hydrate_middleware.py
  73. 50 50
      tests/units/test_app.py
  74. 161 0
      tests/units/test_prerequisites.py
  75. 2 2
      tests/units/test_route.py
  76. 14 14
      tests/units/test_state.py
  77. 1 1
      tests/units/test_state_tree.py
  78. 1 1
      tests/units/test_testing.py
  79. 2 2
      tests/units/test_var.py

+ 13 - 5
.github/workflows/benchmarks.yml

@@ -81,18 +81,22 @@ jobs:
       matrix:
       matrix:
         # Show OS combos first in GUI
         # Show OS combos first in GUI
         os: [ubuntu-latest, windows-latest, macos-latest]
         os: [ubuntu-latest, windows-latest, macos-latest]
-        python-version: ['3.10.16', '3.11.11', '3.12.8']
+        python-version: ["3.10.16", "3.11.11", "3.12.8"]
         exclude:
         exclude:
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.10.16'
+            python-version: "3.10.16"
+          - os: windows-latest
+            python-version: "3.11.11"
           # keep only one python version for MacOS
           # keep only one python version for MacOS
           - os: macos-latest
           - os: macos-latest
-            python-version: '3.10.16'
+            python-version: "3.10.16"
           - os: macos-latest
           - os: macos-latest
             python-version: "3.11.11"
             python-version: "3.11.11"
         include:
         include:
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.10.11'
+            python-version: "3.10.11"
+          - os: windows-latest
+            python-version: "3.11.9"
 
 
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     steps:
     steps:
@@ -155,7 +159,11 @@ jobs:
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     steps:
     steps:
       - uses: actions/checkout@v4
       - uses: actions/checkout@v4
-
+      - name: Set up python
+        id: setup-python
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ matrix.python-version }}
       - name: Install Poetry
       - name: Install Poetry
         uses: snok/install-poetry@v1
         uses: snok/install-poetry@v1
         with:
         with:

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

@@ -55,7 +55,7 @@ jobs:
           path: reflex-web
           path: reflex-web
       - name: Install Requirements for reflex-web
       - name: Install Requirements for reflex-web
         working-directory: ./reflex-web
         working-directory: ./reflex-web
-        run: poetry run uv pip install -r requirements.txt
+        run: poetry run uv pip install $(grep -ivE "reflex " requirements.txt)
       - name: Install additional dependencies for DB access
       - name: Install additional dependencies for DB access
         run: poetry run uv pip install psycopg
         run: poetry run uv pip install psycopg
       - name: Init Website for reflex-web
       - name: Init Website for reflex-web
@@ -73,7 +73,7 @@ jobs:
           echo "$outdated"
           echo "$outdated"
 
 
           # Ignore 3rd party dependencies that are not updated.
           # Ignore 3rd party dependencies that are not updated.
-          filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images' || true)
+          filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images|ag-grid' || true)
           no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
           no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
 
 
 
 

+ 0 - 7
.github/workflows/integration_app_harness.yml

@@ -50,14 +50,7 @@ jobs:
       - run: poetry run uv pip install pyvirtualdisplay pillow pytest-split pytest-retry
       - run: poetry run uv pip install pyvirtualdisplay pillow pytest-split pytest-retry
       - name: Run app harness tests
       - name: Run app harness tests
         env:
         env:
-          SCREENSHOT_DIR: /tmp/screenshots/${{ matrix.state_manager }}/${{ matrix.python-version }}/${{ matrix.split_index }}
           REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
           REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
         run: |
         run: |
           poetry run playwright install chromium
           poetry run playwright install chromium
           poetry run pytest tests/integration --retries 3 --maxfail=5 --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()
-        with:
-          name: failed_test_screenshots
-          path: /tmp/screenshots

+ 1 - 1
benchmarks/benchmark_package_size.py

@@ -21,7 +21,7 @@ def get_package_size(venv_path: Path, os_name):
         ValueError: when venv does not exist or python version is None.
         ValueError: when venv does not exist or python version is None.
     """
     """
     python_version = get_python_version(venv_path, os_name)
     python_version = get_python_version(venv_path, os_name)
-    print("Python version:", python_version)  # noqa: T201
+    print("Python version:", python_version)
     if python_version is None:
     if python_version is None:
         raise ValueError("Error: Failed to determine Python version.")
         raise ValueError("Error: Failed to determine Python version.")
 
 

+ 3 - 3
benchmarks/test_benchmark_compile_components.py

@@ -122,7 +122,7 @@ def AppWithTenComponentsOnePage():
     def index() -> rx.Component:
     def index() -> rx.Component:
         return rx.center(rx.vstack(*render_component(1)))
         return rx.center(rx.vstack(*render_component(1)))
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     app.add_page(index)
     app.add_page(index)
 
 
 
 
@@ -133,7 +133,7 @@ def AppWithHundredComponentOnePage():
     def index() -> rx.Component:
     def index() -> rx.Component:
         return rx.center(rx.vstack(*render_component(100)))
         return rx.center(rx.vstack(*render_component(100)))
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     app.add_page(index)
     app.add_page(index)
 
 
 
 
@@ -144,7 +144,7 @@ def AppWithThousandComponentsOnePage():
     def index() -> rx.Component:
     def index() -> rx.Component:
         return rx.center(rx.vstack(*render_component(1000)))
         return rx.center(rx.vstack(*render_component(1000)))
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     app.add_page(index)
     app.add_page(index)
 
 
 
 

+ 5 - 5
benchmarks/test_benchmark_compile_pages.py

@@ -162,7 +162,7 @@ def AppWithOnePage():
             height="100vh",
             height="100vh",
         )
         )
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     app.add_page(index)
     app.add_page(index)
 
 
 
 
@@ -170,7 +170,7 @@ def AppWithTenPages():
     """A reflex app with 10 pages."""
     """A reflex app with 10 pages."""
     import reflex as rx
     import reflex as rx
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     render_multiple_pages(app, 10)
     render_multiple_pages(app, 10)
 
 
 
 
@@ -178,7 +178,7 @@ def AppWithHundredPages():
     """A reflex app with 100 pages."""
     """A reflex app with 100 pages."""
     import reflex as rx
     import reflex as rx
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     render_multiple_pages(app, 100)
     render_multiple_pages(app, 100)
 
 
 
 
@@ -186,7 +186,7 @@ def AppWithThousandPages():
     """A reflex app with Thousand pages."""
     """A reflex app with Thousand pages."""
     import reflex as rx
     import reflex as rx
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     render_multiple_pages(app, 1000)
     render_multiple_pages(app, 1000)
 
 
 
 
@@ -194,7 +194,7 @@ def AppWithTenThousandPages():
     """A reflex app with ten thousand pages."""
     """A reflex app with ten thousand pages."""
     import reflex as rx
     import reflex as rx
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     render_multiple_pages(app, 10000)
     render_multiple_pages(app, 10000)
 
 
 
 

+ 0 - 2
poetry.lock

@@ -461,7 +461,6 @@ files = [
     {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_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_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_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_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-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-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"},
@@ -472,7 +471,6 @@ files = [
     {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_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_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_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_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-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-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"},

+ 6 - 3
pyproject.toml

@@ -87,15 +87,18 @@ reportIncompatibleVariableOverride = false
 target-version = "py39"
 target-version = "py39"
 output-format = "concise"
 output-format = "concise"
 lint.isort.split-on-trailing-comma = false
 lint.isort.split-on-trailing-comma = false
-lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "PERF", "PTH", "RUF", "SIM", "T", "TRY", "W"]
+lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "N", "PERF", "PTH", "RUF", "SIM", "T", "TRY", "W"]
 lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012", "TRY0"]
 lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012", "TRY0"]
 lint.pydocstyle.convention = "google"
 lint.pydocstyle.convention = "google"
 
 
 [tool.ruff.lint.per-file-ignores]
 [tool.ruff.lint.per-file-ignores]
 "__init__.py" = ["F401"]
 "__init__.py" = ["F401"]
-"tests/*.py" = ["D100", "D103", "D104", "B018", "PERF", "T"]
+"tests/*.py" = ["D100", "D103", "D104", "B018", "PERF", "T", "N"]
+"benchmarks/*.py" = ["D100", "D103", "D104", "B018", "PERF", "T", "N"]
 "reflex/.templates/*.py" = ["D100", "D103", "D104"]
 "reflex/.templates/*.py" = ["D100", "D103", "D104"]
-"*.pyi" = ["D301", "D415", "D417", "D418", "E742"]
+"*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N"]
+"pyi_generator.py" = ["N802"]
+"reflex/constants/*.py" = ["N"]
 "*/blank.py" = ["I001"]
 "*/blank.py" = ["I001"]
 
 
 [tool.pytest.ini_options]
 [tool.pytest.ini_options]

+ 1 - 4
reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js

@@ -16,10 +16,7 @@ export default function RadixThemesColorModeProvider({ children }) {
     if (isDevMode) {
     if (isDevMode) {
       const lastCompiledTimeInLocalStorage =
       const lastCompiledTimeInLocalStorage =
         localStorage.getItem("last_compiled_time");
         localStorage.getItem("last_compiled_time");
-      if (
-        lastCompiledTimeInLocalStorage &&
-        lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp
-      ) {
+      if (lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp) {
         // on app startup, make sure the application color mode is persisted correctly.
         // on app startup, make sure the application color mode is persisted correctly.
         setTheme(defaultColorMode);
         setTheme(defaultColorMode);
         localStorage.setItem("last_compiled_time", lastCompiledTimeStamp);
         localStorage.setItem("last_compiled_time", lastCompiledTimeStamp);

+ 94 - 59
reflex/app.py

@@ -251,36 +251,36 @@ class App(MiddlewareMixin, LifespanMixin):
     # Attributes to add to the html root tag of every page.
     # Attributes to add to the html root tag of every page.
     html_custom_attrs: Optional[Dict[str, str]] = None
     html_custom_attrs: Optional[Dict[str, str]] = None
 
 
-    # A map from a route to an unevaluated page. PRIVATE.
-    unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
+    # A map from a route to an unevaluated page.
+    _unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
         default_factory=dict
         default_factory=dict
     )
     )
 
 
-    # A map from a page route to the component to render. Users should use `add_page`. PRIVATE.
-    pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
+    # A map from a page route to the component to render. Users should use `add_page`.
+    _pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
 
 
-    # The backend API object. PRIVATE.
-    api: FastAPI = None  # type: ignore
+    # The backend API object.
+    _api: FastAPI | None = None
 
 
-    # The state class to use for the app. PRIVATE.
-    state: Optional[Type[BaseState]] = None
+    # The state class to use for the app.
+    _state: Optional[Type[BaseState]] = None
 
 
     # Class to manage many client states.
     # Class to manage many client states.
     _state_manager: Optional[StateManager] = None
     _state_manager: Optional[StateManager] = None
 
 
-    # Mapping from a route to event handlers to trigger when the page loads. PRIVATE.
-    load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
+    # Mapping from a route to event handlers to trigger when the page loads.
+    _load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
         default_factory=dict
         default_factory=dict
     )
     )
 
 
-    # Admin dashboard to view and manage the database. PRIVATE.
+    # Admin dashboard to view and manage the database.
     admin_dash: Optional[AdminDash] = None
     admin_dash: Optional[AdminDash] = None
 
 
-    # The async server name space. PRIVATE.
-    event_namespace: Optional[EventNamespace] = None
+    # The async server name space.
+    _event_namespace: Optional[EventNamespace] = None
 
 
-    # Background tasks that are currently running. PRIVATE.
-    background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
+    # Background tasks that are currently running.
+    _background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
 
 
     # Frontend Error Handler Function
     # Frontend Error Handler Function
     frontend_exception_handler: Callable[[Exception], None] = (
     frontend_exception_handler: Callable[[Exception], None] = (
@@ -292,6 +292,24 @@ class App(MiddlewareMixin, LifespanMixin):
         [Exception], Union[EventSpec, List[EventSpec], None]
         [Exception], Union[EventSpec, List[EventSpec], None]
     ] = default_backend_exception_handler
     ] = default_backend_exception_handler
 
 
+    @property
+    def api(self) -> FastAPI | None:
+        """Get the backend api.
+
+        Returns:
+            The backend api.
+        """
+        return self._api
+
+    @property
+    def event_namespace(self) -> EventNamespace | None:
+        """Get the event namespace.
+
+        Returns:
+            The event namespace.
+        """
+        return self._event_namespace
+
     def __post_init__(self):
     def __post_init__(self):
         """Initialize the app.
         """Initialize the app.
 
 
@@ -311,7 +329,7 @@ class App(MiddlewareMixin, LifespanMixin):
             set_breakpoints(self.style.pop("breakpoints"))
             set_breakpoints(self.style.pop("breakpoints"))
 
 
         # Set up the API.
         # Set up the API.
-        self.api = FastAPI(lifespan=self._run_lifespan_tasks)
+        self._api = FastAPI(lifespan=self._run_lifespan_tasks)
         self._add_cors()
         self._add_cors()
         self._add_default_endpoints()
         self._add_default_endpoints()
 
 
@@ -334,8 +352,8 @@ class App(MiddlewareMixin, LifespanMixin):
 
 
     def _enable_state(self) -> None:
     def _enable_state(self) -> None:
         """Enable state for the app."""
         """Enable state for the app."""
-        if not self.state:
-            self.state = State
+        if not self._state:
+            self._state = State
             self._setup_state()
             self._setup_state()
 
 
     def _setup_state(self) -> None:
     def _setup_state(self) -> None:
@@ -344,13 +362,13 @@ class App(MiddlewareMixin, LifespanMixin):
         Raises:
         Raises:
             RuntimeError: If the socket server is invalid.
             RuntimeError: If the socket server is invalid.
         """
         """
-        if not self.state:
+        if not self._state:
             return
             return
 
 
         config = get_config()
         config = get_config()
 
 
         # Set up the state manager.
         # Set up the state manager.
-        self._state_manager = StateManager.create(state=self.state)
+        self._state_manager = StateManager.create(state=self._state)
 
 
         # Set up the Socket.IO AsyncServer.
         # Set up the Socket.IO AsyncServer.
         if not self.sio:
         if not self.sio:
@@ -381,12 +399,13 @@ class App(MiddlewareMixin, LifespanMixin):
         namespace = config.get_event_namespace()
         namespace = config.get_event_namespace()
 
 
         # Create the event namespace and attach the main app. Not related to any paths.
         # Create the event namespace and attach the main app. Not related to any paths.
-        self.event_namespace = EventNamespace(namespace, self)
+        self._event_namespace = EventNamespace(namespace, self)
 
 
         # Register the event namespace with the socket.
         # Register the event namespace with the socket.
         self.sio.register_namespace(self.event_namespace)
         self.sio.register_namespace(self.event_namespace)
         # Mount the socket app with the API.
         # Mount the socket app with the API.
-        self.api.mount(str(constants.Endpoint.EVENT), socket_app)
+        if self.api:
+            self.api.mount(str(constants.Endpoint.EVENT), socket_app)
 
 
         # Check the exception handlers
         # Check the exception handlers
         self._validate_exception_handlers()
         self._validate_exception_handlers()
@@ -397,24 +416,35 @@ class App(MiddlewareMixin, LifespanMixin):
         Returns:
         Returns:
             The string representation of the app.
             The string representation of the app.
         """
         """
-        return f"<App state={self.state.__name__ if self.state else None}>"
+        return f"<App state={self._state.__name__ if self._state else None}>"
 
 
     def __call__(self) -> FastAPI:
     def __call__(self) -> FastAPI:
         """Run the backend api instance.
         """Run the backend api instance.
 
 
+        Raises:
+            ValueError: If the app has not been initialized.
+
         Returns:
         Returns:
             The backend api.
             The backend api.
         """
         """
+        if not self.api:
+            raise ValueError("The app has not been initialized.")
         return self.api
         return self.api
 
 
     def _add_default_endpoints(self):
     def _add_default_endpoints(self):
         """Add default api endpoints (ping)."""
         """Add default api endpoints (ping)."""
         # To test the server.
         # To test the server.
+        if not self.api:
+            return
+
         self.api.get(str(constants.Endpoint.PING))(ping)
         self.api.get(str(constants.Endpoint.PING))(ping)
         self.api.get(str(constants.Endpoint.HEALTH))(health)
         self.api.get(str(constants.Endpoint.HEALTH))(health)
 
 
     def _add_optional_endpoints(self):
     def _add_optional_endpoints(self):
         """Add optional api endpoints (_upload)."""
         """Add optional api endpoints (_upload)."""
+        if not self.api:
+            return
+
         if Upload.is_used:
         if Upload.is_used:
             # To upload files.
             # To upload files.
             self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
             self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
@@ -432,6 +462,8 @@ class App(MiddlewareMixin, LifespanMixin):
 
 
     def _add_cors(self):
     def _add_cors(self):
         """Add CORS middleware to the app."""
         """Add CORS middleware to the app."""
+        if not self.api:
+            return
         self.api.add_middleware(
         self.api.add_middleware(
             cors.CORSMiddleware,
             cors.CORSMiddleware,
             allow_credentials=True,
             allow_credentials=True,
@@ -521,13 +553,13 @@ class App(MiddlewareMixin, LifespanMixin):
         # Check if the route given is valid
         # Check if the route given is valid
         verify_route_validity(route)
         verify_route_validity(route)
 
 
-        if route in self.unevaluated_pages and environment.RELOAD_CONFIG.is_set():
+        if route in self._unevaluated_pages and environment.RELOAD_CONFIG.is_set():
             # when the app is reloaded(typically for app harness tests), we should maintain
             # when the app is reloaded(typically for app harness tests), we should maintain
             # the latest render function of a route.This applies typically to decorated pages
             # the latest render function of a route.This applies typically to decorated pages
             # since they are only added when app._compile is called.
             # since they are only added when app._compile is called.
-            self.unevaluated_pages.pop(route)
+            self._unevaluated_pages.pop(route)
 
 
-        if route in self.unevaluated_pages:
+        if route in self._unevaluated_pages:
             route_name = (
             route_name = (
                 f"`{route}` or `/`"
                 f"`{route}` or `/`"
                 if route == constants.PageNames.INDEX_ROUTE
                 if route == constants.PageNames.INDEX_ROUTE
@@ -540,15 +572,15 @@ class App(MiddlewareMixin, LifespanMixin):
 
 
         # Setup dynamic args for the route.
         # Setup dynamic args for the route.
         # this state assignment is only required for tests using the deprecated state kwarg for App
         # this state assignment is only required for tests using the deprecated state kwarg for App
-        state = self.state if self.state else State
+        state = self._state if self._state else State
         state.setup_dynamic_args(get_route_args(route))
         state.setup_dynamic_args(get_route_args(route))
 
 
         if on_load:
         if on_load:
-            self.load_events[route] = (
+            self._load_events[route] = (
                 on_load if isinstance(on_load, list) else [on_load]
                 on_load if isinstance(on_load, list) else [on_load]
             )
             )
 
 
-        self.unevaluated_pages[route] = UnevaluatedPage(
+        self._unevaluated_pages[route] = UnevaluatedPage(
             component=component,
             component=component,
             route=route,
             route=route,
             title=title,
             title=title,
@@ -563,10 +595,10 @@ class App(MiddlewareMixin, LifespanMixin):
 
 
         Args:
         Args:
             route: The route of the page to compile.
             route: The route of the page to compile.
-            save_page: If True, the compiled page is saved to self.pages.
+            save_page: If True, the compiled page is saved to self._pages.
         """
         """
         component, enable_state = compiler.compile_unevaluated_page(
         component, enable_state = compiler.compile_unevaluated_page(
-            route, self.unevaluated_pages[route], self.state, self.style, self.theme
+            route, self._unevaluated_pages[route], self._state, self.style, self.theme
         )
         )
 
 
         if enable_state:
         if enable_state:
@@ -575,7 +607,7 @@ class App(MiddlewareMixin, LifespanMixin):
         # Add the page.
         # Add the page.
         self._check_routes_conflict(route)
         self._check_routes_conflict(route)
         if save_page:
         if save_page:
-            self.pages[route] = component
+            self._pages[route] = component
 
 
     def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
     def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
         """Get the load events for a route.
         """Get the load events for a route.
@@ -589,7 +621,7 @@ class App(MiddlewareMixin, LifespanMixin):
         route = route.lstrip("/")
         route = route.lstrip("/")
         if route == "":
         if route == "":
             route = constants.PageNames.INDEX_ROUTE
             route = constants.PageNames.INDEX_ROUTE
-        return self.load_events.get(route, [])
+        return self._load_events.get(route, [])
 
 
     def _check_routes_conflict(self, new_route: str):
     def _check_routes_conflict(self, new_route: str):
         """Verify if there is any conflict between the new route and any existing route.
         """Verify if there is any conflict between the new route and any existing route.
@@ -613,7 +645,7 @@ class App(MiddlewareMixin, LifespanMixin):
             constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
             constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
             constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
             constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
         )
         )
-        for route in self.pages:
+        for route in self._pages:
             replaced_route = replace_brackets_with_keywords(route)
             replaced_route = replace_brackets_with_keywords(route)
             for rw, r, nr in zip(
             for rw, r, nr in zip(
                 replaced_route.split("/"), route.split("/"), new_route.split("/")
                 replaced_route.split("/"), route.split("/"), new_route.split("/")
@@ -671,6 +703,9 @@ class App(MiddlewareMixin, LifespanMixin):
     def _setup_admin_dash(self):
     def _setup_admin_dash(self):
         """Setup the admin dash."""
         """Setup the admin dash."""
         # Get the admin dash.
         # Get the admin dash.
+        if not self.api:
+            return
+
         admin_dash = self.admin_dash
         admin_dash = self.admin_dash
 
 
         if admin_dash and admin_dash.models:
         if admin_dash and admin_dash.models:
@@ -775,10 +810,10 @@ class App(MiddlewareMixin, LifespanMixin):
 
 
     def _setup_overlay_component(self):
     def _setup_overlay_component(self):
         """If a State is not used and no overlay_component is specified, do not render the connection modal."""
         """If a State is not used and no overlay_component is specified, do not render the connection modal."""
-        if self.state is None and self.overlay_component is default_overlay_component:
+        if self._state is None and self.overlay_component is default_overlay_component:
             self.overlay_component = None
             self.overlay_component = None
-        for k, component in self.pages.items():
-            self.pages[k] = self._add_overlay_to_component(component)
+        for k, component in self._pages.items():
+            self._pages[k] = self._add_overlay_to_component(component)
 
 
     def _add_error_boundary_to_component(self, component: Component) -> Component:
     def _add_error_boundary_to_component(self, component: Component) -> Component:
         if self.error_boundary is None:
         if self.error_boundary is None:
@@ -790,14 +825,14 @@ class App(MiddlewareMixin, LifespanMixin):
 
 
     def _setup_error_boundary(self):
     def _setup_error_boundary(self):
         """If a State is not used and no error_boundary is specified, do not render the error boundary."""
         """If a State is not used and no error_boundary is specified, do not render the error boundary."""
-        if self.state is None and self.error_boundary is default_error_boundary:
+        if self._state is None and self.error_boundary is default_error_boundary:
             self.error_boundary = None
             self.error_boundary = None
 
 
-        for k, component in self.pages.items():
+        for k, component in self._pages.items():
             # Skip the 404 page
             # Skip the 404 page
             if k == constants.Page404.SLUG:
             if k == constants.Page404.SLUG:
                 continue
                 continue
-            self.pages[k] = self._add_error_boundary_to_component(component)
+            self._pages[k] = self._add_error_boundary_to_component(component)
 
 
     def _apply_decorated_pages(self):
     def _apply_decorated_pages(self):
         """Add @rx.page decorated pages to the app.
         """Add @rx.page decorated pages to the app.
@@ -823,11 +858,11 @@ class App(MiddlewareMixin, LifespanMixin):
         Raises:
         Raises:
             VarDependencyError: When a computed var has an invalid dependency.
             VarDependencyError: When a computed var has an invalid dependency.
         """
         """
-        if not self.state:
+        if not self._state:
             return
             return
 
 
         if not state:
         if not state:
-            state = self.state
+            state = self._state
 
 
         for var in state.computed_vars.values():
         for var in state.computed_vars.values():
             if not var._cache:
             if not var._cache:
@@ -853,13 +888,13 @@ class App(MiddlewareMixin, LifespanMixin):
         """
         """
         from reflex.utils.exceptions import ReflexRuntimeError
         from reflex.utils.exceptions import ReflexRuntimeError
 
 
-        self.pages = {}
+        self._pages = {}
 
 
         def get_compilation_time() -> str:
         def get_compilation_time() -> str:
             return str(datetime.now().time()).split(".")[0]
             return str(datetime.now().time()).split(".")[0]
 
 
         # Render a default 404 page if the user didn't supply one
         # Render a default 404 page if the user didn't supply one
-        if constants.Page404.SLUG not in self.unevaluated_pages:
+        if constants.Page404.SLUG not in self._unevaluated_pages:
             self.add_page(route=constants.Page404.SLUG)
             self.add_page(route=constants.Page404.SLUG)
 
 
         # Fix up the style.
         # Fix up the style.
@@ -877,7 +912,7 @@ class App(MiddlewareMixin, LifespanMixin):
 
 
         should_compile = self._should_compile()
         should_compile = self._should_compile()
 
 
-        for route in self.unevaluated_pages:
+        for route in self._unevaluated_pages:
             console.debug(f"Evaluating page: {route}")
             console.debug(f"Evaluating page: {route}")
             self._compile_page(route, save_page=should_compile)
             self._compile_page(route, save_page=should_compile)
 
 
@@ -904,7 +939,7 @@ class App(MiddlewareMixin, LifespanMixin):
         progress.start()
         progress.start()
         task = progress.add_task(
         task = progress.add_task(
             f"[{get_compilation_time()}] Compiling:",
             f"[{get_compilation_time()}] Compiling:",
-            total=len(self.pages)
+            total=len(self._pages)
             + fixed_pages_within_executor
             + fixed_pages_within_executor
             + adhoc_steps_without_executor,
             + adhoc_steps_without_executor,
         )
         )
@@ -923,7 +958,7 @@ class App(MiddlewareMixin, LifespanMixin):
 
 
         # This has to happen before compiling stateful components as that
         # This has to happen before compiling stateful components as that
         # prevents recursive functions from reaching all components.
         # prevents recursive functions from reaching all components.
-        for component in self.pages.values():
+        for component in self._pages.values():
             # Add component._get_all_imports() to all_imports.
             # Add component._get_all_imports() to all_imports.
             all_imports.update(component._get_all_imports())
             all_imports.update(component._get_all_imports())
 
 
@@ -938,12 +973,12 @@ class App(MiddlewareMixin, LifespanMixin):
             stateful_components_path,
             stateful_components_path,
             stateful_components_code,
             stateful_components_code,
             page_components,
             page_components,
-        ) = compiler.compile_stateful_components(self.pages.values())
+        ) = compiler.compile_stateful_components(self._pages.values())
 
 
         progress.advance(task)
         progress.advance(task)
 
 
         # Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
         # Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
-        if code_uses_state_contexts(stateful_components_code) and self.state is None:
+        if code_uses_state_contexts(stateful_components_code) and self._state is None:
             raise ReflexRuntimeError(
             raise ReflexRuntimeError(
                 "To access rx.State in frontend components, at least one "
                 "To access rx.State in frontend components, at least one "
                 "subclass of rx.State must be defined in the app."
                 "subclass of rx.State must be defined in the app."
@@ -980,10 +1015,10 @@ class App(MiddlewareMixin, LifespanMixin):
                 max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
                 max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
             )
             )
 
 
-        for route, component in zip(self.pages, page_components):
+        for route, component in zip(self._pages, page_components):
             ExecutorSafeFunctions.COMPONENTS[route] = component
             ExecutorSafeFunctions.COMPONENTS[route] = component
 
 
-        ExecutorSafeFunctions.STATE = self.state
+        ExecutorSafeFunctions.STATE = self._state
 
 
         with executor:
         with executor:
             result_futures = []
             result_futures = []
@@ -993,7 +1028,7 @@ class App(MiddlewareMixin, LifespanMixin):
                 result_futures.append(f)
                 result_futures.append(f)
 
 
             # Compile the pre-compiled pages.
             # Compile the pre-compiled pages.
-            for route in self.pages:
+            for route in self._pages:
                 _submit_work(
                 _submit_work(
                     ExecutorSafeFunctions.compile_page,
                     ExecutorSafeFunctions.compile_page,
                     route,
                     route,
@@ -1028,7 +1063,7 @@ class App(MiddlewareMixin, LifespanMixin):
 
 
         # Compile the contexts.
         # Compile the contexts.
         compile_results.append(
         compile_results.append(
-            compiler.compile_contexts(self.state, self.theme),
+            compiler.compile_contexts(self._state, self.theme),
         )
         )
         if self.theme is not None:
         if self.theme is not None:
             # Fix #2992 by removing the top-level appearance prop
             # Fix #2992 by removing the top-level appearance prop
@@ -1150,9 +1185,9 @@ class App(MiddlewareMixin, LifespanMixin):
                 )
                 )
 
 
         task = asyncio.create_task(_coro())
         task = asyncio.create_task(_coro())
-        self.background_tasks.add(task)
+        self._background_tasks.add(task)
         # Clean up task from background_tasks set when complete.
         # Clean up task from background_tasks set when complete.
-        task.add_done_callback(self.background_tasks.discard)
+        task.add_done_callback(self._background_tasks.discard)
         return task
         return task
 
 
     def _validate_exception_handlers(self):
     def _validate_exception_handlers(self):
@@ -1162,11 +1197,11 @@ class App(MiddlewareMixin, LifespanMixin):
             ValueError: If the custom exception handlers are invalid.
             ValueError: If the custom exception handlers are invalid.
 
 
         """
         """
-        FRONTEND_ARG_SPEC = {
+        frontend_arg_spec = {
             "exception": Exception,
             "exception": Exception,
         }
         }
 
 
-        BACKEND_ARG_SPEC = {
+        backend_arg_spec = {
             "exception": Exception,
             "exception": Exception,
         }
         }
 
 
@@ -1174,8 +1209,8 @@ class App(MiddlewareMixin, LifespanMixin):
             ["frontend", "backend"],
             ["frontend", "backend"],
             [self.frontend_exception_handler, self.backend_exception_handler],
             [self.frontend_exception_handler, self.backend_exception_handler],
             [
             [
-                FRONTEND_ARG_SPEC,
-                BACKEND_ARG_SPEC,
+                frontend_arg_spec,
+                backend_arg_spec,
             ],
             ],
         ):
         ):
             if hasattr(handler_fn, "__name__"):
             if hasattr(handler_fn, "__name__"):

+ 3 - 3
reflex/app_mixins/lifespan.py

@@ -12,7 +12,7 @@ from typing import Callable, Coroutine, Set, Union
 from fastapi import FastAPI
 from fastapi import FastAPI
 
 
 from reflex.utils import console
 from reflex.utils import console
-from reflex.utils.exceptions import InvalidLifespanTaskType
+from reflex.utils.exceptions import InvalidLifespanTaskTypeError
 
 
 from .mixin import AppMixin
 from .mixin import AppMixin
 
 
@@ -64,10 +64,10 @@ class LifespanMixin(AppMixin):
             task_kwargs: The kwargs of the task.
             task_kwargs: The kwargs of the task.
 
 
         Raises:
         Raises:
-            InvalidLifespanTaskType: If the task is a generator function.
+            InvalidLifespanTaskTypeError: If the task is a generator function.
         """
         """
         if inspect.isgeneratorfunction(task) or inspect.isasyncgenfunction(task):
         if inspect.isgeneratorfunction(task) or inspect.isasyncgenfunction(task):
-            raise InvalidLifespanTaskType(
+            raise InvalidLifespanTaskTypeError(
                 f"Task {task.__name__} of type generator must be decorated with contextlib.asynccontextmanager."
                 f"Task {task.__name__} of type generator must be decorated with contextlib.asynccontextmanager."
             )
             )
 
 

+ 0 - 3
reflex/app_module_for_backend.py

@@ -5,14 +5,12 @@ Only the app attribute is explicitly exposed.
 from concurrent.futures import ThreadPoolExecutor
 from concurrent.futures import ThreadPoolExecutor
 
 
 from reflex import constants
 from reflex import constants
-from reflex.utils import telemetry
 from reflex.utils.exec import is_prod_mode
 from reflex.utils.exec import is_prod_mode
 from reflex.utils.prerequisites import get_and_validate_app
 from reflex.utils.prerequisites import get_and_validate_app
 
 
 if constants.CompileVars.APP != "app":
 if constants.CompileVars.APP != "app":
     raise AssertionError("unexpected variable name for 'app'")
     raise AssertionError("unexpected variable name for 'app'")
 
 
-telemetry.send("compile")
 app, app_module = get_and_validate_app(reload=False)
 app, app_module = get_and_validate_app(reload=False)
 # For py3.9 compatibility when redis is used, we MUST add any decorator pages
 # 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).
 # before compiling the app in a thread to avoid event loop error (REF-2172).
@@ -31,6 +29,5 @@ del app_module
 del compile_future
 del compile_future
 del get_and_validate_app
 del get_and_validate_app
 del is_prod_mode
 del is_prod_mode
-del telemetry
 del constants
 del constants
 del ThreadPoolExecutor
 del ThreadPoolExecutor

+ 1 - 1
reflex/components/core/html.py

@@ -14,7 +14,7 @@ class Html(Div):
     """
     """
 
 
     # The HTML to render.
     # The HTML to render.
-    dangerouslySetInnerHTML: Var[Dict[str, str]]
+    dangerouslySetInnerHTML: Var[Dict[str, str]]  # noqa: N815
 
 
     @classmethod
     @classmethod
     def create(cls, *children, **props):
     def create(cls, *children, **props):

+ 1 - 1
reflex/components/core/upload.py

@@ -190,7 +190,7 @@ class GhostUpload(Fragment):
 class Upload(MemoizationLeaf):
 class Upload(MemoizationLeaf):
     """A file upload component."""
     """A file upload component."""
 
 
-    library = "react-dropzone@14.2.10"
+    library = "react-dropzone@14.3.5"
 
 
     tag = ""
     tag = ""
 
 

+ 1 - 1
reflex/components/datadisplay/code.py

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

+ 5 - 3
reflex/components/dynamic.py

@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Union
 
 
 from reflex import constants
 from reflex import constants
 from reflex.utils import imports
 from reflex.utils import imports
-from reflex.utils.exceptions import DynamicComponentMissingLibrary
+from reflex.utils.exceptions import DynamicComponentMissingLibraryError
 from reflex.utils.format import format_library_name
 from reflex.utils.format import format_library_name
 from reflex.utils.serializers import serializer
 from reflex.utils.serializers import serializer
 from reflex.vars import Var, get_unique_variable_name
 from reflex.vars import Var, get_unique_variable_name
@@ -36,13 +36,15 @@ def bundle_library(component: Union["Component", str]):
         component: The component to bundle the library with.
         component: The component to bundle the library with.
 
 
     Raises:
     Raises:
-        DynamicComponentMissingLibrary: Raised when a dynamic component is missing a library.
+        DynamicComponentMissingLibraryError: Raised when a dynamic component is missing a library.
     """
     """
     if isinstance(component, str):
     if isinstance(component, str):
         bundled_libraries.add(component)
         bundled_libraries.add(component)
         return
         return
     if component.library is None:
     if component.library is None:
-        raise DynamicComponentMissingLibrary("Component must have a library to bundle.")
+        raise DynamicComponentMissingLibraryError(
+            "Component must have a library to bundle."
+        )
     bundled_libraries.add(format_library_name(component.library))
     bundled_libraries.add(format_library_name(component.library))
 
 
 
 

+ 13 - 3
reflex/components/next/image.py

@@ -3,11 +3,13 @@
 from typing import Any, Literal, Optional, Union
 from typing import Any, Literal, Optional, Union
 
 
 from reflex.event import EventHandler, no_args_event_spec
 from reflex.event import EventHandler, no_args_event_spec
-from reflex.utils import types
+from reflex.utils import console, types
 from reflex.vars.base import Var
 from reflex.vars.base import Var
 
 
 from .base import NextComponent
 from .base import NextComponent
 
 
+DEFAULT_W_H = "100%"
+
 
 
 class Image(NextComponent):
 class Image(NextComponent):
     """Display an image."""
     """Display an image."""
@@ -53,7 +55,7 @@ class Image(NextComponent):
     loading: Var[Literal["lazy", "eager"]]
     loading: Var[Literal["lazy", "eager"]]
 
 
     # A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
     # A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
-    blurDataURL: Var[str]
+    blur_data_url: Var[str]
 
 
     # Fires when the image has loaded.
     # Fires when the image has loaded.
     on_load: EventHandler[no_args_event_spec]
     on_load: EventHandler[no_args_event_spec]
@@ -80,8 +82,16 @@ class Image(NextComponent):
         Returns:
         Returns:
             _type_: _description_
             _type_: _description_
         """
         """
+        if "blurDataURL" in props:
+            console.deprecate(
+                feature_name="blurDataURL",
+                reason="Use blur_data_url instead",
+                deprecation_version="0.7.0",
+                removal_version="0.8.0",
+            )
+            props["blur_data_url"] = props.pop("blurDataURL")
+
         style = props.get("style", {})
         style = props.get("style", {})
-        DEFAULT_W_H = "100%"
 
 
         def check_prop_type(prop_name, prop_value):
         def check_prop_type(prop_name, prop_value):
             if types.check_prop_in_allowed_types(prop_value, allowed_types=[int]):
             if types.check_prop_in_allowed_types(prop_value, allowed_types=[int]):

+ 4 - 2
reflex/components/next/image.pyi

@@ -11,6 +11,8 @@ from reflex.vars.base import Var
 
 
 from .base import NextComponent
 from .base import NextComponent
 
 
+DEFAULT_W_H = "100%"
+
 class Image(NextComponent):
 class Image(NextComponent):
     @overload
     @overload
     @classmethod
     @classmethod
@@ -30,7 +32,7 @@ class Image(NextComponent):
         loading: Optional[
         loading: Optional[
             Union[Literal["eager", "lazy"], Var[Literal["eager", "lazy"]]]
             Union[Literal["eager", "lazy"], Var[Literal["eager", "lazy"]]]
         ] = None,
         ] = None,
-        blurDataURL: Optional[Union[Var[str], str]] = None,
+        blur_data_url: Optional[Union[Var[str], str]] = None,
         style: Optional[Style] = None,
         style: Optional[Style] = None,
         key: Optional[Any] = None,
         key: Optional[Any] = None,
         id: Optional[Any] = None,
         id: Optional[Any] = None,
@@ -71,7 +73,7 @@ class Image(NextComponent):
             priority: When true, the image will be considered high priority and preload. Lazy loading is automatically disabled for images using priority.
             priority: When true, the image will be considered high priority and preload. Lazy loading is automatically disabled for images using priority.
             placeholder: A placeholder to use while the image is loading. Possible values are blur, empty, or data:image/.... Defaults to empty.
             placeholder: A placeholder to use while the image is loading. Possible values are blur, empty, or data:image/.... Defaults to empty.
             loading: The loading behavior of the image. Defaults to lazy. Can hurt performance, recommended to use `priority` instead.
             loading: The loading behavior of the image. Defaults to lazy. Can hurt performance, recommended to use `priority` instead.
-            blurDataURL: A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
+            blur_data_url: A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
             on_load: Fires when the image has loaded.
             on_load: Fires when the image has loaded.
             on_error: Fires when the image has an error.
             on_error: Fires when the image has an error.
             style: The style of the component.
             style: The style of the component.

+ 1 - 1
reflex/components/plotly/plotly.py

@@ -95,7 +95,7 @@ class Plotly(NoSSRComponent):
 
 
     library = "react-plotly.js@2.6.0"
     library = "react-plotly.js@2.6.0"
 
 
-    lib_dependencies: List[str] = ["plotly.js@2.35.2"]
+    lib_dependencies: List[str] = ["plotly.js@2.35.3"]
 
 
     tag = "Plot"
     tag = "Plot"
 
 

+ 4 - 4
reflex/components/radix/primitives/accordion.py

@@ -485,11 +485,11 @@ to {
         Returns:
         Returns:
             The style of the component.
             The style of the component.
         """
         """
-        slideDown = LiteralVar.create(
+        slide_down = LiteralVar.create(
             "${slideDown} var(--animation-duration) var(--animation-easing)",
             "${slideDown} var(--animation-duration) var(--animation-easing)",
         )
         )
 
 
-        slideUp = LiteralVar.create(
+        slide_up = LiteralVar.create(
             "${slideUp} var(--animation-duration) var(--animation-easing)",
             "${slideUp} var(--animation-duration) var(--animation-easing)",
         )
         )
 
 
@@ -503,8 +503,8 @@ to {
                 "display": "block",
                 "display": "block",
                 "height": "var(--space-3)",
                 "height": "var(--space-3)",
             },
             },
-            "&[data-state='open']": {"animation": slideDown},
-            "&[data-state='closed']": {"animation": slideUp},
+            "&[data-state='open']": {"animation": slide_down},
+            "&[data-state='closed']": {"animation": slide_up},
             _inherited_variant_selector("classic"): {
             _inherited_variant_selector("classic"): {
                 "color": "var(--accent-contrast)",
                 "color": "var(--accent-contrast)",
             },
             },

+ 1 - 1
reflex/components/radix/primitives/drawer.py

@@ -66,7 +66,7 @@ class DrawerRoot(DrawerComponent):
     scroll_lock_timeout: Var[int]
     scroll_lock_timeout: Var[int]
 
 
     # When `True`, it prevents scroll restoration. Defaults to `True`.
     # When `True`, it prevents scroll restoration. Defaults to `True`.
-    preventScrollRestoration: Var[bool]
+    prevent_scroll_restoration: Var[bool]
 
 
     # Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
     # Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
     should_scale_background: Var[bool]
     should_scale_background: Var[bool]

+ 4 - 4
reflex/components/radix/primitives/drawer.pyi

@@ -81,7 +81,7 @@ class DrawerRoot(DrawerComponent):
         snap_points: Optional[List[Union[float, str]]] = None,
         snap_points: Optional[List[Union[float, str]]] = None,
         fade_from_index: Optional[Union[Var[int], int]] = None,
         fade_from_index: Optional[Union[Var[int], int]] = None,
         scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
         scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
-        preventScrollRestoration: Optional[Union[Var[bool], bool]] = None,
+        prevent_scroll_restoration: Optional[Union[Var[bool], bool]] = None,
         should_scale_background: Optional[Union[Var[bool], bool]] = None,
         should_scale_background: Optional[Union[Var[bool], bool]] = None,
         close_threshold: Optional[Union[Var[float], float]] = None,
         close_threshold: Optional[Union[Var[float], float]] = None,
         as_child: Optional[Union[Var[bool], bool]] = None,
         as_child: Optional[Union[Var[bool], bool]] = None,
@@ -129,7 +129,7 @@ class DrawerRoot(DrawerComponent):
             snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
             snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
             fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
             fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
             scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
             scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
-            preventScrollRestoration: When `True`, it prevents scroll restoration. Defaults to `True`.
+            prevent_scroll_restoration: When `True`, it prevents scroll restoration. Defaults to `True`.
             should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
             should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
             close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
             close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
             as_child: Change the default rendered element for the one passed as a child.
             as_child: Change the default rendered element for the one passed as a child.
@@ -567,7 +567,7 @@ class Drawer(ComponentNamespace):
         snap_points: Optional[List[Union[float, str]]] = None,
         snap_points: Optional[List[Union[float, str]]] = None,
         fade_from_index: Optional[Union[Var[int], int]] = None,
         fade_from_index: Optional[Union[Var[int], int]] = None,
         scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
         scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
-        preventScrollRestoration: Optional[Union[Var[bool], bool]] = None,
+        prevent_scroll_restoration: Optional[Union[Var[bool], bool]] = None,
         should_scale_background: Optional[Union[Var[bool], bool]] = None,
         should_scale_background: Optional[Union[Var[bool], bool]] = None,
         close_threshold: Optional[Union[Var[float], float]] = None,
         close_threshold: Optional[Union[Var[float], float]] = None,
         as_child: Optional[Union[Var[bool], bool]] = None,
         as_child: Optional[Union[Var[bool], bool]] = None,
@@ -615,7 +615,7 @@ class Drawer(ComponentNamespace):
             snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
             snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
             fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
             fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
             scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
             scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
-            preventScrollRestoration: When `True`, it prevents scroll restoration. Defaults to `True`.
+            prevent_scroll_restoration: When `True`, it prevents scroll restoration. Defaults to `True`.
             should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
             should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
             close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
             close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
             as_child: Change the default rendered element for the one passed as a child.
             as_child: Change the default rendered element for the one passed as a child.

+ 2 - 2
reflex/components/radix/themes/components/icon_button.py

@@ -22,6 +22,8 @@ from ..base import (
 
 
 LiteralButtonSize = Literal["1", "2", "3", "4"]
 LiteralButtonSize = Literal["1", "2", "3", "4"]
 
 
+RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
+
 
 
 class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
 class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
     """A button designed specifically for usage with a single icon."""
     """A button designed specifically for usage with a single icon."""
@@ -72,8 +74,6 @@ class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
                 "IconButton requires a child icon. Pass a string as the first child or a rx.icon."
                 "IconButton requires a child icon. Pass a string as the first child or a rx.icon."
             )
             )
         if "size" in props:
         if "size" in props:
-            RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
-
             if isinstance(props["size"], str):
             if isinstance(props["size"], str):
                 children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]]
                 children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]]
             else:
             else:

+ 1 - 0
reflex/components/radix/themes/components/icon_button.pyi

@@ -14,6 +14,7 @@ from reflex.vars.base import Var
 from ..base import RadixLoadingProp, RadixThemesComponent
 from ..base import RadixLoadingProp, RadixThemesComponent
 
 
 LiteralButtonSize = Literal["1", "2", "3", "4"]
 LiteralButtonSize = Literal["1", "2", "3", "4"]
+RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
 
 
 class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
 class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
     @overload
     @overload

+ 3 - 1
reflex/components/radix/themes/components/tooltip.py

@@ -28,6 +28,9 @@ LiteralStickyType = Literal[
 ]
 ]
 
 
 
 
+ARIA_LABEL_KEY = "aria_label"
+
+
 # The Tooltip inherits props from the Tooltip.Root, Tooltip.Portal, Tooltip.Content
 # The Tooltip inherits props from the Tooltip.Root, Tooltip.Portal, Tooltip.Content
 class Tooltip(RadixThemesComponent):
 class Tooltip(RadixThemesComponent):
     """Floating element that provides a control with contextual information via pointer or focus."""
     """Floating element that provides a control with contextual information via pointer or focus."""
@@ -104,7 +107,6 @@ class Tooltip(RadixThemesComponent):
         Returns:
         Returns:
             The created component.
             The created component.
         """
         """
-        ARIA_LABEL_KEY = "aria_label"
         if props.get(ARIA_LABEL_KEY) is not None:
         if props.get(ARIA_LABEL_KEY) is not None:
             props[format.to_kebab_case(ARIA_LABEL_KEY)] = props.pop(ARIA_LABEL_KEY)
             props[format.to_kebab_case(ARIA_LABEL_KEY)] = props.pop(ARIA_LABEL_KEY)
 
 

+ 1 - 0
reflex/components/radix/themes/components/tooltip.pyi

@@ -14,6 +14,7 @@ from ..base import RadixThemesComponent
 LiteralSideType = Literal["top", "right", "bottom", "left"]
 LiteralSideType = Literal["top", "right", "bottom", "left"]
 LiteralAlignType = Literal["start", "center", "end"]
 LiteralAlignType = Literal["start", "center", "end"]
 LiteralStickyType = Literal["partial", "always"]
 LiteralStickyType = Literal["partial", "always"]
+ARIA_LABEL_KEY = "aria_label"
 
 
 class Tooltip(RadixThemesComponent):
 class Tooltip(RadixThemesComponent):
     @overload
     @overload

+ 2 - 2
reflex/components/recharts/general.py

@@ -250,10 +250,10 @@ class Cell(Recharts):
     alias = "RechartsCell"
     alias = "RechartsCell"
 
 
     # The presentation attribute of a rectangle in bar or a sector in pie.
     # The presentation attribute of a rectangle in bar or a sector in pie.
-    fill: Var[str]
+    fill: Var[str | Color]
 
 
     # The presentation attribute of a rectangle in bar or a sector in pie.
     # The presentation attribute of a rectangle in bar or a sector in pie.
-    stroke: Var[str]
+    stroke: Var[str | Color]
 
 
 
 
 responsive_container = ResponsiveContainer.create
 responsive_container = ResponsiveContainer.create

+ 2 - 2
reflex/components/recharts/general.pyi

@@ -488,8 +488,8 @@ class Cell(Recharts):
     def create(  # type: ignore
     def create(  # type: ignore
         cls,
         cls,
         *children,
         *children,
-        fill: Optional[Union[Var[str], str]] = None,
-        stroke: Optional[Union[Var[str], str]] = None,
+        fill: Optional[Union[Color, Var[Union[Color, str]], str]] = None,
+        stroke: Optional[Union[Color, Var[Union[Color, str]], str]] = None,
         style: Optional[Style] = None,
         style: Optional[Style] = None,
         key: Optional[Any] = None,
         key: Optional[Any] = None,
         id: Optional[Any] = None,
         id: Optional[Any] = None,

+ 1 - 1
reflex/components/recharts/polar.py

@@ -73,7 +73,7 @@ class Pie(Recharts):
     data: Var[List[Dict[str, Any]]]
     data: Var[List[Dict[str, Any]]]
 
 
     # Valid children components
     # Valid children components
-    _valid_children: List[str] = ["Cell", "LabelList"]
+    _valid_children: List[str] = ["Cell", "LabelList", "Bare"]
 
 
     # Stoke color. Default: rx.color("accent", 9)
     # Stoke color. Default: rx.color("accent", 9)
     stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
     stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))

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

@@ -1,6 +1,6 @@
 """A component that wraps a recharts lib."""
 """A component that wraps a recharts lib."""
 
 
-from typing import Dict, Literal
+from typing import Literal
 
 
 from reflex.components.component import Component, MemoizationLeaf, NoSSRComponent
 from reflex.components.component import Component, MemoizationLeaf, NoSSRComponent
 
 
@@ -8,16 +8,16 @@ from reflex.components.component import Component, MemoizationLeaf, NoSSRCompone
 class Recharts(Component):
 class Recharts(Component):
     """A component that wraps a recharts lib."""
     """A component that wraps a recharts lib."""
 
 
-    library = "recharts@2.13.0"
+    library = "recharts@2.15.0"
 
 
-    def _get_style(self) -> Dict:
+    def _get_style(self) -> dict:
         return {"wrapperStyle": self.style}
         return {"wrapperStyle": self.style}
 
 
 
 
 class RechartsCharts(NoSSRComponent, MemoizationLeaf):
 class RechartsCharts(NoSSRComponent, MemoizationLeaf):
     """A component that wraps a recharts lib."""
     """A component that wraps a recharts lib."""
 
 
-    library = "recharts@2.13.0"
+    library = "recharts@2.15.0"
 
 
 
 
 LiteralAnimationEasing = Literal["ease", "ease-in", "ease-out", "ease-in-out", "linear"]
 LiteralAnimationEasing = Literal["ease", "ease-in", "ease-out", "ease-in-out", "linear"]

+ 1 - 1
reflex/components/sonner/toast.py

@@ -167,7 +167,7 @@ class ToastProps(PropsBase, NoExtrasAllowedProps):
 class Toaster(Component):
 class Toaster(Component):
     """A Toaster Component for displaying toast notifications."""
     """A Toaster Component for displaying toast notifications."""
 
 
-    library: str = "sonner@1.7.1"
+    library: str = "sonner@1.7.2"
 
 
     tag = "Toaster"
     tag = "Toaster"
 
 

+ 4 - 7
reflex/config.py

@@ -390,7 +390,7 @@ class EnvVar(Generic[T]):
             os.environ[self.name] = str(value)
             os.environ[self.name] = str(value)
 
 
 
 
-class env_var:  # type: ignore
+class env_var:  # type: ignore # noqa: N801
     """Descriptor for environment variables."""
     """Descriptor for environment variables."""
 
 
     name: str
     name: str
@@ -556,9 +556,6 @@ class EnvironmentVariables:
     # Arguments to pass to the app harness driver.
     # Arguments to pass to the app harness driver.
     APP_HARNESS_DRIVER_ARGS: EnvVar[str] = env_var("")
     APP_HARNESS_DRIVER_ARGS: EnvVar[str] = env_var("")
 
 
-    # Where to save screenshots when tests fail.
-    SCREENSHOT_DIR: EnvVar[Optional[Path]] = env_var(None)
-
     # Whether to check for outdated package versions.
     # Whether to check for outdated package versions.
     REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True)
     REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True)
 
 
@@ -826,16 +823,16 @@ class Config(Base):
         if "api_url" not in self._non_default_attributes:
         if "api_url" not in self._non_default_attributes:
             # If running in Github Codespaces, override API_URL
             # If running in Github Codespaces, override API_URL
             codespace_name = os.getenv("CODESPACE_NAME")
             codespace_name = os.getenv("CODESPACE_NAME")
-            GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN = os.getenv(
+            github_codespaces_port_forwarding_domain = os.getenv(
                 "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"
                 "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"
             )
             )
             # If running on Replit.com interactively, override API_URL to ensure we maintain the backend_port
             # If running on Replit.com interactively, override API_URL to ensure we maintain the backend_port
             replit_dev_domain = os.getenv("REPLIT_DEV_DOMAIN")
             replit_dev_domain = os.getenv("REPLIT_DEV_DOMAIN")
             backend_port = kwargs.get("backend_port", self.backend_port)
             backend_port = kwargs.get("backend_port", self.backend_port)
-            if codespace_name and GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:
+            if codespace_name and github_codespaces_port_forwarding_domain:
                 self.api_url = (
                 self.api_url = (
                     f"https://{codespace_name}-{kwargs.get('backend_port', self.backend_port)}"
                     f"https://{codespace_name}-{kwargs.get('backend_port', self.backend_port)}"
-                    f".{GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}"
+                    f".{github_codespaces_port_forwarding_domain}"
                 )
                 )
             elif replit_dev_domain and backend_port:
             elif replit_dev_domain and backend_port:
                 self.api_url = f"https://{replit_dev_domain}:{backend_port}"
                 self.api_url = f"https://{replit_dev_domain}:{backend_port}"

+ 2 - 0
reflex/constants/compiler.py

@@ -28,6 +28,8 @@ class Ext(SimpleNamespace):
     ZIP = ".zip"
     ZIP = ".zip"
     # The extension for executable files on Windows.
     # The extension for executable files on Windows.
     EXE = ".exe"
     EXE = ".exe"
+    # The extension for markdown files.
+    MD = ".md"
 
 
 
 
 class CompileVars(SimpleNamespace):
 class CompileVars(SimpleNamespace):

+ 9 - 9
reflex/constants/installer.py

@@ -37,10 +37,10 @@ class Bun(SimpleNamespace):
     """Bun constants."""
     """Bun constants."""
 
 
     # The Bun version.
     # The Bun version.
-    VERSION = "1.1.29"
+    VERSION = "1.2.0"
 
 
     # Min Bun Version
     # Min Bun Version
-    MIN_VERSION = "0.7.0"
+    MIN_VERSION = "1.1.0"
 
 
     # URL to bun install script.
     # URL to bun install script.
     INSTALL_URL = "https://raw.githubusercontent.com/reflex-dev/reflex/main/scripts/bun_install.sh"
     INSTALL_URL = "https://raw.githubusercontent.com/reflex-dev/reflex/main/scripts/bun_install.sh"
@@ -178,21 +178,21 @@ class PackageJson(SimpleNamespace):
     PATH = "package.json"
     PATH = "package.json"
 
 
     DEPENDENCIES = {
     DEPENDENCIES = {
-        "@babel/standalone": "7.26.0",
-        "@emotion/react": "11.13.3",
-        "axios": "1.7.7",
+        "@babel/standalone": "7.26.6",
+        "@emotion/react": "11.14.0",
+        "axios": "1.7.9",
         "json5": "2.2.3",
         "json5": "2.2.3",
-        "next": "15.1.4",
+        "next": "15.1.6",
         "next-sitemap": "4.2.3",
         "next-sitemap": "4.2.3",
-        "next-themes": "0.4.3",
+        "next-themes": "0.4.4",
         "react": "18.3.1",
         "react": "18.3.1",
         "react-dom": "18.3.1",
         "react-dom": "18.3.1",
-        "react-focus-lock": "2.13.2",
+        "react-focus-lock": "2.13.5",
         "socket.io-client": "4.8.1",
         "socket.io-client": "4.8.1",
         "universal-cookie": "7.2.2",
         "universal-cookie": "7.2.2",
     }
     }
     DEV_DEPENDENCIES = {
     DEV_DEPENDENCIES = {
         "autoprefixer": "10.4.20",
         "autoprefixer": "10.4.20",
-        "postcss": "8.4.49",
+        "postcss": "8.5.1",
         "postcss-import": "16.1.0",
         "postcss-import": "16.1.0",
     }
     }

+ 1 - 1
reflex/constants/style.py

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

+ 4 - 4
reflex/event.py

@@ -536,10 +536,10 @@ class JavasciptKeyboardEvent:
     """Interface for a Javascript KeyboardEvent https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent."""
     """Interface for a Javascript KeyboardEvent https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent."""
 
 
     key: str = ""
     key: str = ""
-    altKey: bool = False
-    ctrlKey: bool = False
-    metaKey: bool = False
-    shiftKey: bool = False
+    altKey: bool = False  # noqa: N815
+    ctrlKey: bool = False  # noqa: N815
+    metaKey: bool = False  # noqa: N815
+    shiftKey: bool = False  # noqa: N815
 
 
 
 
 def input_event(e: Var[JavascriptInputEvent]) -> Tuple[Var[str]]:
 def input_event(e: Var[JavascriptInputEvent]) -> Tuple[Var[str]]:

+ 51 - 23
reflex/experimental/client_state.py

@@ -34,6 +34,18 @@ def _client_state_ref(var_name: str) -> str:
     return f"refs['_client_state_{var_name}']"
     return f"refs['_client_state_{var_name}']"
 
 
 
 
+def _client_state_ref_dict(var_name: str) -> str:
+    """Get the ref path for a ClientStateVar.
+
+    Args:
+        var_name: The name of the variable.
+
+    Returns:
+        An accessor for ClientStateVar ref as a string.
+    """
+    return f"refs['_client_state_dict_{var_name}']"
+
+
 @dataclasses.dataclass(
 @dataclasses.dataclass(
     eq=False,
     eq=False,
     frozen=True,
     frozen=True,
@@ -115,10 +127,41 @@ class ClientStateVar(Var):
             "react": [ImportVar(tag="useState"), ImportVar(tag="useId")],
             "react": [ImportVar(tag="useState"), ImportVar(tag="useId")],
         }
         }
         if global_ref:
         if global_ref:
-            hooks[f"{_client_state_ref(var_name)} ??= {{}}"] = None
-            hooks[f"{_client_state_ref(setter_name)} ??= {{}}"] = None
-            hooks[f"{_client_state_ref(var_name)}[{id_name}] = {var_name}"] = None
-            hooks[f"{_client_state_ref(setter_name)}[{id_name}] = {setter_name}"] = None
+            arg_name = get_unique_variable_name()
+            func = ArgsFunctionOperationBuilder.create(
+                args_names=(arg_name,),
+                return_expr=Var("Array.prototype.forEach.call")
+                .to(FunctionVar)
+                .call(
+                    (
+                        Var("Object.values")
+                        .to(FunctionVar)
+                        .call(Var(_client_state_ref_dict(setter_name)))
+                        .to(list)
+                        .to(list)
+                    )
+                    + Var.create(
+                        [
+                            Var(
+                                f"(value) => {{ {_client_state_ref(var_name)} = value; }}"
+                            )
+                        ]
+                    ).to(list),
+                    ArgsFunctionOperationBuilder.create(
+                        args_names=("setter",),
+                        return_expr=Var("setter").to(FunctionVar).call(Var(arg_name)),
+                    ),
+                ),
+            )
+
+            hooks[f"{_client_state_ref(setter_name)} = {func!s}"] = None
+            hooks[f"{_client_state_ref(var_name)} ??= {var_name!s}"] = None
+            hooks[f"{_client_state_ref_dict(var_name)} ??= {{}}"] = None
+            hooks[f"{_client_state_ref_dict(setter_name)} ??= {{}}"] = None
+            hooks[f"{_client_state_ref_dict(var_name)}[{id_name}] = {var_name}"] = None
+            hooks[
+                f"{_client_state_ref_dict(setter_name)}[{id_name}] = {setter_name}"
+            ] = None
             imports.update(_refs_import)
             imports.update(_refs_import)
         return cls(
         return cls(
             _js_expr="",
             _js_expr="",
@@ -150,7 +193,7 @@ class ClientStateVar(Var):
         return (
         return (
             Var(
             Var(
                 _js_expr=(
                 _js_expr=(
-                    _client_state_ref(self._getter_name) + f"[{self._id_name}]"
+                    _client_state_ref_dict(self._getter_name) + f"[{self._id_name}]"
                     if self._global_ref
                     if self._global_ref
                     else self._getter_name
                     else self._getter_name
                 ),
                 ),
@@ -179,26 +222,11 @@ class ClientStateVar(Var):
         """
         """
         _var_data = VarData(imports=_refs_import if self._global_ref else {})
         _var_data = VarData(imports=_refs_import if self._global_ref else {})
 
 
-        arg_name = get_unique_variable_name()
         setter = (
         setter = (
-            ArgsFunctionOperationBuilder.create(
-                args_names=(arg_name,),
-                return_expr=Var("Array.prototype.forEach.call")
-                .to(FunctionVar)
-                .call(
-                    Var("Object.values")
-                    .to(FunctionVar)
-                    .call(Var(_client_state_ref(self._setter_name))),
-                    ArgsFunctionOperationBuilder.create(
-                        args_names=("setter",),
-                        return_expr=Var("setter").to(FunctionVar).call(Var(arg_name)),
-                    ),
-                ),
-                _var_data=_var_data,
-            )
+            Var(_client_state_ref(self._setter_name))
             if self._global_ref
             if self._global_ref
-            else Var(self._setter_name, _var_data=_var_data).to(FunctionVar)
-        )
+            else Var(self._setter_name, _var_data=_var_data)
+        ).to(FunctionVar)
 
 
         if value is not NoValue:
         if value is not NoValue:
             # This is a hack to make it work like an EventSpec taking an arg
             # This is a hack to make it work like an EventSpec taking an arg

+ 4 - 4
reflex/experimental/hooks.py

@@ -26,7 +26,7 @@ def const(name, value) -> Var:
     return Var(_js_expr=f"const {name} = {value}")
     return Var(_js_expr=f"const {name} = {value}")
 
 
 
 
-def useCallback(func, deps) -> Var:
+def useCallback(func, deps) -> Var:  # noqa: N802
     """Create a useCallback hook with a function and dependencies.
     """Create a useCallback hook with a function and dependencies.
 
 
     Args:
     Args:
@@ -42,7 +42,7 @@ def useCallback(func, deps) -> Var:
     )
     )
 
 
 
 
-def useContext(context) -> Var:
+def useContext(context) -> Var:  # noqa: N802
     """Create a useContext hook with a context.
     """Create a useContext hook with a context.
 
 
     Args:
     Args:
@@ -57,7 +57,7 @@ def useContext(context) -> Var:
     )
     )
 
 
 
 
-def useRef(default) -> Var:
+def useRef(default) -> Var:  # noqa: N802
     """Create a useRef hook with a default value.
     """Create a useRef hook with a default value.
 
 
     Args:
     Args:
@@ -72,7 +72,7 @@ def useRef(default) -> Var:
     )
     )
 
 
 
 
-def useState(var_name, default=None) -> Var:
+def useState(var_name, default=None) -> Var:  # noqa: N802
     """Create a useState hook with a variable name and setter name.
     """Create a useState hook with a variable name and setter name.
 
 
     Args:
     Args:

+ 1 - 1
reflex/experimental/layout.pyi

@@ -109,7 +109,7 @@ class DrawerSidebar(DrawerRoot):
         snap_points: Optional[List[Union[float, str]]] = None,
         snap_points: Optional[List[Union[float, str]]] = None,
         fade_from_index: Optional[Union[Var[int], int]] = None,
         fade_from_index: Optional[Union[Var[int], int]] = None,
         scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
         scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
-        preventScrollRestoration: Optional[Union[Var[bool], bool]] = None,
+        prevent_scroll_restoration: Optional[Union[Var[bool], bool]] = None,
         should_scale_background: Optional[Union[Var[bool], bool]] = None,
         should_scale_background: Optional[Union[Var[bool], bool]] = None,
         close_threshold: Optional[Union[Var[float], float]] = None,
         close_threshold: Optional[Union[Var[float], float]] = None,
         as_child: Optional[Union[Var[bool], bool]] = None,
         as_child: Optional[Union[Var[bool], bool]] = None,

+ 14 - 0
reflex/reflex.py

@@ -573,6 +573,20 @@ def deploy(
     )
     )
 
 
 
 
+@cli.command()
+def rename(
+    new_name: str = typer.Argument(..., help="The new name for the app."),
+    loglevel: constants.LogLevel = typer.Option(
+        config.loglevel, help="The log level to use."
+    ),
+):
+    """Rename the app in the current directory."""
+    from reflex.utils import prerequisites
+
+    prerequisites.validate_app_name(new_name)
+    prerequisites.rename_app(new_name, loglevel)
+
+
 cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
 cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
 cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")
 cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")
 cli.add_typer(
 cli.add_typer(

+ 23 - 24
reflex/state.py

@@ -93,14 +93,14 @@ from reflex.event import (
 )
 )
 from reflex.utils import console, format, path_ops, prerequisites, types
 from reflex.utils import console, format, path_ops, prerequisites, types
 from reflex.utils.exceptions import (
 from reflex.utils.exceptions import (
-    ComputedVarShadowsBaseVars,
-    ComputedVarShadowsStateVar,
-    DynamicComponentInvalidSignature,
-    DynamicRouteArgShadowsStateVar,
-    EventHandlerShadowsBuiltInStateMethod,
+    ComputedVarShadowsBaseVarsError,
+    ComputedVarShadowsStateVarError,
+    DynamicComponentInvalidSignatureError,
+    DynamicRouteArgShadowsStateVarError,
+    EventHandlerShadowsBuiltInStateMethodError,
     ImmutableStateError,
     ImmutableStateError,
     InvalidLockWarningThresholdError,
     InvalidLockWarningThresholdError,
-    InvalidStateManagerMode,
+    InvalidStateManagerModeError,
     LockExpiredError,
     LockExpiredError,
     ReflexRuntimeError,
     ReflexRuntimeError,
     SetUndefinedStateVarError,
     SetUndefinedStateVarError,
@@ -815,7 +815,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         """Check for shadow methods and raise error if any.
         """Check for shadow methods and raise error if any.
 
 
         Raises:
         Raises:
-            EventHandlerShadowsBuiltInStateMethod: When an event handler shadows an inbuilt state method.
+            EventHandlerShadowsBuiltInStateMethodError: When an event handler shadows an inbuilt state method.
         """
         """
         overridden_methods = set()
         overridden_methods = set()
         state_base_functions = cls._get_base_functions()
         state_base_functions = cls._get_base_functions()
@@ -829,7 +829,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
                 overridden_methods.add(method.__name__)
                 overridden_methods.add(method.__name__)
 
 
         for method_name in overridden_methods:
         for method_name in overridden_methods:
-            raise EventHandlerShadowsBuiltInStateMethod(
+            raise EventHandlerShadowsBuiltInStateMethodError(
                 f"The event handler name `{method_name}` shadows a builtin State method; use a different name instead"
                 f"The event handler name `{method_name}` shadows a builtin State method; use a different name instead"
             )
             )
 
 
@@ -838,11 +838,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         """Check for shadow base vars and raise error if any.
         """Check for shadow base vars and raise error if any.
 
 
         Raises:
         Raises:
-            ComputedVarShadowsBaseVars: When a computed var shadows a base var.
+            ComputedVarShadowsBaseVarsError: When a computed var shadows a base var.
         """
         """
         for computed_var_ in cls._get_computed_vars():
         for computed_var_ in cls._get_computed_vars():
             if computed_var_._js_expr in cls.__annotations__:
             if computed_var_._js_expr in cls.__annotations__:
-                raise ComputedVarShadowsBaseVars(
+                raise ComputedVarShadowsBaseVarsError(
                     f"The computed var name `{computed_var_._js_expr}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead"
                     f"The computed var name `{computed_var_._js_expr}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead"
                 )
                 )
 
 
@@ -851,14 +851,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         """Check for shadow computed vars and raise error if any.
         """Check for shadow computed vars and raise error if any.
 
 
         Raises:
         Raises:
-            ComputedVarShadowsStateVar: When a computed var shadows another.
+            ComputedVarShadowsStateVarError: When a computed var shadows another.
         """
         """
         for name, cv in cls.__dict__.items():
         for name, cv in cls.__dict__.items():
             if not is_computed_var(cv):
             if not is_computed_var(cv):
                 continue
                 continue
             name = cv._js_expr
             name = cv._js_expr
             if name in cls.inherited_vars or name in cls.inherited_backend_vars:
             if name in cls.inherited_vars or name in cls.inherited_backend_vars:
-                raise ComputedVarShadowsStateVar(
+                raise ComputedVarShadowsStateVarError(
                     f"The computed var name `{cv._js_expr}` shadows a var in {cls.__module__}.{cls.__name__}; use a different name instead"
                     f"The computed var name `{cv._js_expr}` shadows a var in {cls.__module__}.{cls.__name__}; use a different name instead"
                 )
                 )
 
 
@@ -1218,14 +1218,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             args: a dict of args
             args: a dict of args
 
 
         Raises:
         Raises:
-            DynamicRouteArgShadowsStateVar: If a dynamic arg is shadowing an existing var.
+            DynamicRouteArgShadowsStateVarError: If a dynamic arg is shadowing an existing var.
         """
         """
         for arg in args:
         for arg in args:
             if (
             if (
                 arg in cls.computed_vars
                 arg in cls.computed_vars
                 and not isinstance(cls.computed_vars[arg], DynamicRouteVar)
                 and not isinstance(cls.computed_vars[arg], DynamicRouteVar)
             ) or arg in cls.base_vars:
             ) or arg in cls.base_vars:
-                raise DynamicRouteArgShadowsStateVar(
+                raise DynamicRouteArgShadowsStateVarError(
                     f"Dynamic route arg '{arg}' is shadowing an existing var in {cls.__module__}.{cls.__name__}"
                     f"Dynamic route arg '{arg}' is shadowing an existing var in {cls.__module__}.{cls.__name__}"
                 )
                 )
         for substate in cls.get_substates():
         for substate in cls.get_substates():
@@ -2353,8 +2353,7 @@ def dynamic(func: Callable[[T], Component]):
         The dynamically generated component.
         The dynamically generated component.
 
 
     Raises:
     Raises:
-        DynamicComponentInvalidSignature: If the function does not have exactly one parameter.
-        DynamicComponentInvalidSignature: If the function does not have a type hint for the state class.
+        DynamicComponentInvalidSignatureError: If the function does not have exactly one parameter or a type hint for the state class.
     """
     """
     number_of_parameters = len(inspect.signature(func).parameters)
     number_of_parameters = len(inspect.signature(func).parameters)
 
 
@@ -2366,12 +2365,12 @@ def dynamic(func: Callable[[T], Component]):
     values = list(func_signature.values())
     values = list(func_signature.values())
 
 
     if number_of_parameters != 1:
     if number_of_parameters != 1:
-        raise DynamicComponentInvalidSignature(
+        raise DynamicComponentInvalidSignatureError(
             "The function must have exactly one parameter, which is the state class."
             "The function must have exactly one parameter, which is the state class."
         )
         )
 
 
     if len(values) != 1:
     if len(values) != 1:
-        raise DynamicComponentInvalidSignature(
+        raise DynamicComponentInvalidSignatureError(
             "You must provide a type hint for the state class in the function."
             "You must provide a type hint for the state class in the function."
         )
         )
 
 
@@ -2878,7 +2877,7 @@ class StateManager(Base, ABC):
             state: The state class to use.
             state: The state class to use.
 
 
         Raises:
         Raises:
-            InvalidStateManagerMode: If the state manager mode is invalid.
+            InvalidStateManagerModeError: If the state manager mode is invalid.
 
 
         Returns:
         Returns:
             The state manager (either disk, memory or redis).
             The state manager (either disk, memory or redis).
@@ -2901,7 +2900,7 @@ class StateManager(Base, ABC):
                     lock_expiration=config.redis_lock_expiration,
                     lock_expiration=config.redis_lock_expiration,
                     lock_warning_threshold=config.redis_lock_warning_threshold,
                     lock_warning_threshold=config.redis_lock_warning_threshold,
                 )
                 )
-        raise InvalidStateManagerMode(
+        raise InvalidStateManagerModeError(
             f"Expected one of: DISK, MEMORY, REDIS, got {config.state_manager_mode}"
             f"Expected one of: DISK, MEMORY, REDIS, got {config.state_manager_mode}"
         )
         )
 
 
@@ -4057,10 +4056,10 @@ def serialize_mutable_proxy(mp: MutableProxy):
     return mp.__wrapped__
     return mp.__wrapped__
 
 
 
 
-_orig_json_JSONEncoder_default = json.JSONEncoder.default
+_orig_json_encoder_default = json.JSONEncoder.default
 
 
 
 
-def _json_JSONEncoder_default_wrapper(self: json.JSONEncoder, o: Any) -> Any:
+def _json_encoder_default_wrapper(self: json.JSONEncoder, o: Any) -> Any:
     """Wrap JSONEncoder.default to handle MutableProxy objects.
     """Wrap JSONEncoder.default to handle MutableProxy objects.
 
 
     Args:
     Args:
@@ -4074,10 +4073,10 @@ def _json_JSONEncoder_default_wrapper(self: json.JSONEncoder, o: Any) -> Any:
         return o.__wrapped__
         return o.__wrapped__
     except AttributeError:
     except AttributeError:
         pass
         pass
-    return _orig_json_JSONEncoder_default(self, o)
+    return _orig_json_encoder_default(self, o)
 
 
 
 
-json.JSONEncoder.default = _json_JSONEncoder_default_wrapper
+json.JSONEncoder.default = _json_encoder_default_wrapper
 
 
 
 
 class ImmutableMutableProxy(MutableProxy):
 class ImmutableMutableProxy(MutableProxy):

+ 6 - 6
reflex/testing.py

@@ -85,7 +85,7 @@ else:
 
 
 
 
 # borrowed from py3.11
 # borrowed from py3.11
-class chdir(contextlib.AbstractContextManager):
+class chdir(contextlib.AbstractContextManager):  # noqa: N801
     """Non thread-safe context manager to change the current working directory."""
     """Non thread-safe context manager to change the current working directory."""
 
 
     def __init__(self, path):
     def __init__(self, path):
@@ -296,9 +296,9 @@ class AppHarness:
             raise RuntimeError("App was not initialized.")
             raise RuntimeError("App was not initialized.")
         if isinstance(self.app_instance._state_manager, StateManagerRedis):
         if isinstance(self.app_instance._state_manager, StateManagerRedis):
             # Create our own redis connection for testing.
             # Create our own redis connection for testing.
-            if self.app_instance.state is None:
+            if self.app_instance._state is None:
                 raise RuntimeError("App state is not initialized.")
                 raise RuntimeError("App state is not initialized.")
-            self.state_manager = StateManagerRedis.create(self.app_instance.state)
+            self.state_manager = StateManagerRedis.create(self.app_instance._state)
         else:
         else:
             self.state_manager = self.app_instance._state_manager
             self.state_manager = self.app_instance._state_manager
 
 
@@ -326,7 +326,7 @@ class AppHarness:
         return _shutdown_redis
         return _shutdown_redis
 
 
     def _start_backend(self, port=0):
     def _start_backend(self, port=0):
-        if self.app_instance is None:
+        if self.app_instance is None or self.app_instance.api is None:
             raise RuntimeError("App was not initialized.")
             raise RuntimeError("App was not initialized.")
         self.backend = uvicorn.Server(
         self.backend = uvicorn.Server(
             uvicorn.Config(
             uvicorn.Config(
@@ -355,12 +355,12 @@ class AppHarness:
                 self.app_instance.state_manager,
                 self.app_instance.state_manager,
                 StateManagerRedis,
                 StateManagerRedis,
             )
             )
-            and self.app_instance.state is not None
+            and self.app_instance._state is not None
         ):
         ):
             with contextlib.suppress(RuntimeError):
             with contextlib.suppress(RuntimeError):
                 await self.app_instance.state_manager.close()
                 await self.app_instance.state_manager.close()
             self.app_instance._state_manager = StateManagerRedis.create(
             self.app_instance._state_manager = StateManagerRedis.create(
-                state=self.app_instance.state,
+                state=self.app_instance._state,
             )
             )
             if not isinstance(self.app_instance.state_manager, StateManagerRedis):
             if not isinstance(self.app_instance.state_manager, StateManagerRedis):
                 raise RuntimeError("Failed to reset state manager.")
                 raise RuntimeError("Failed to reset state manager.")

+ 1 - 4
reflex/utils/codespaces.py

@@ -42,10 +42,7 @@ def codespaces_port_forwarding_domain() -> str | None:
     Returns:
     Returns:
         The domain for port forwarding in Github Codespaces, or None if not running in Codespaces.
         The domain for port forwarding in Github Codespaces, or None if not running in Codespaces.
     """
     """
-    GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN = os.getenv(
-        "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"
-    )
-    return GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN
+    return os.getenv("GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN")
 
 
 
 
 def is_running_in_codespaces() -> bool:
 def is_running_in_codespaces() -> bool:

+ 10 - 10
reflex/utils/exceptions.py

@@ -11,7 +11,7 @@ class ConfigError(ReflexError):
     """Custom exception for config related errors."""
     """Custom exception for config related errors."""
 
 
 
 
-class InvalidStateManagerMode(ReflexError, ValueError):
+class InvalidStateManagerModeError(ReflexError, ValueError):
     """Raised when an invalid state manager mode is provided."""
     """Raised when an invalid state manager mode is provided."""
 
 
 
 
@@ -143,35 +143,35 @@ class EventFnArgMismatchError(ReflexError, TypeError):
     """Raised when the number of args required by an event handler is more than provided by the event trigger."""
     """Raised when the number of args required by an event handler is more than provided by the event trigger."""
 
 
 
 
-class DynamicRouteArgShadowsStateVar(ReflexError, NameError):
+class DynamicRouteArgShadowsStateVarError(ReflexError, NameError):
     """Raised when a dynamic route arg shadows a state var."""
     """Raised when a dynamic route arg shadows a state var."""
 
 
 
 
-class ComputedVarShadowsStateVar(ReflexError, NameError):
+class ComputedVarShadowsStateVarError(ReflexError, NameError):
     """Raised when a computed var shadows a state var."""
     """Raised when a computed var shadows a state var."""
 
 
 
 
-class ComputedVarShadowsBaseVars(ReflexError, NameError):
+class ComputedVarShadowsBaseVarsError(ReflexError, NameError):
     """Raised when a computed var shadows a base var."""
     """Raised when a computed var shadows a base var."""
 
 
 
 
-class EventHandlerShadowsBuiltInStateMethod(ReflexError, NameError):
+class EventHandlerShadowsBuiltInStateMethodError(ReflexError, NameError):
     """Raised when an event handler shadows a built-in state method."""
     """Raised when an event handler shadows a built-in state method."""
 
 
 
 
-class GeneratedCodeHasNoFunctionDefs(ReflexError):
+class GeneratedCodeHasNoFunctionDefsError(ReflexError):
     """Raised when refactored code generated with flexgen has no functions defined."""
     """Raised when refactored code generated with flexgen has no functions defined."""
 
 
 
 
-class PrimitiveUnserializableToJSON(ReflexError, ValueError):
+class PrimitiveUnserializableToJSONError(ReflexError, ValueError):
     """Raised when a primitive type is unserializable to JSON. Usually with NaN and Infinity."""
     """Raised when a primitive type is unserializable to JSON. Usually with NaN and Infinity."""
 
 
 
 
-class InvalidLifespanTaskType(ReflexError, TypeError):
+class InvalidLifespanTaskTypeError(ReflexError, TypeError):
     """Raised when an invalid task type is registered as a lifespan task."""
     """Raised when an invalid task type is registered as a lifespan task."""
 
 
 
 
-class DynamicComponentMissingLibrary(ReflexError, ValueError):
+class DynamicComponentMissingLibraryError(ReflexError, ValueError):
     """Raised when a dynamic component is missing a library."""
     """Raised when a dynamic component is missing a library."""
 
 
 
 
@@ -187,7 +187,7 @@ class EnvironmentVarValueError(ReflexError, ValueError):
     """Raised when an environment variable is set to an invalid value."""
     """Raised when an environment variable is set to an invalid value."""
 
 
 
 
-class DynamicComponentInvalidSignature(ReflexError, TypeError):
+class DynamicComponentInvalidSignatureError(ReflexError, TypeError):
     """Raised when a dynamic component has an invalid signature."""
     """Raised when a dynamic component has an invalid signature."""
 
 
 
 

+ 4 - 4
reflex/utils/exec.py

@@ -364,11 +364,11 @@ def run_uvicorn_backend_prod(host, port, loglevel):
 
 
     app_module = get_app_module()
     app_module = get_app_module()
 
 
-    RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split()
-    RUN_BACKEND_PROD_WINDOWS = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split()
+    run_backend_prod = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split()
+    run_backend_prod_windows = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split()
     command = (
     command = (
         [
         [
-            *RUN_BACKEND_PROD_WINDOWS,
+            *run_backend_prod_windows,
             "--host",
             "--host",
             host,
             host,
             "--port",
             "--port",
@@ -377,7 +377,7 @@ def run_uvicorn_backend_prod(host, port, loglevel):
         ]
         ]
         if constants.IS_WINDOWS
         if constants.IS_WINDOWS
         else [
         else [
-            *RUN_BACKEND_PROD,
+            *run_backend_prod,
             "--bind",
             "--bind",
             f"{host}:{port}",
             f"{host}:{port}",
             "--threads",
             "--threads",

+ 166 - 3
reflex/utils/prerequisites.py

@@ -7,6 +7,7 @@ import dataclasses
 import functools
 import functools
 import importlib
 import importlib
 import importlib.metadata
 import importlib.metadata
+import importlib.util
 import json
 import json
 import os
 import os
 import platform
 import platform
@@ -37,7 +38,7 @@ from reflex.compiler import templates
 from reflex.config import Config, environment, get_config
 from reflex.config import Config, environment, get_config
 from reflex.utils import console, net, path_ops, processes, redir
 from reflex.utils import console, net, path_ops, processes, redir
 from reflex.utils.exceptions import (
 from reflex.utils.exceptions import (
-    GeneratedCodeHasNoFunctionDefs,
+    GeneratedCodeHasNoFunctionDefsError,
     SystemPackageMissingError,
     SystemPackageMissingError,
 )
 )
 from reflex.utils.format import format_library_name
 from reflex.utils.format import format_library_name
@@ -463,6 +464,167 @@ def validate_app_name(app_name: str | None = None) -> str:
     return app_name
     return app_name
 
 
 
 
+def rename_path_up_tree(full_path: str | Path, old_name: str, new_name: str) -> Path:
+    """Rename all instances of `old_name` in the path (file and directories) to `new_name`.
+    The renaming stops when we reach the directory containing `rxconfig.py`.
+
+    Args:
+        full_path: The full path to start renaming from.
+        old_name: The name to be replaced.
+        new_name: The replacement name.
+
+    Returns:
+         The updated path after renaming.
+    """
+    current_path = Path(full_path)
+    new_path = None
+
+    while True:
+        directory, base = current_path.parent, current_path.name
+        # Stop renaming when we reach the root dir (which contains rxconfig.py)
+        if current_path.is_dir() and (current_path / "rxconfig.py").exists():
+            new_path = current_path
+            break
+
+        if old_name == base.removesuffix(constants.Ext.PY):
+            new_base = base.replace(old_name, new_name)
+            new_path = directory / new_base
+            current_path.rename(new_path)
+            console.debug(f"Renamed {current_path} -> {new_path}")
+            current_path = new_path
+        else:
+            new_path = current_path
+
+        # Move up the directory tree
+        current_path = directory
+
+    return new_path
+
+
+def rename_app(new_app_name: str, loglevel: constants.LogLevel):
+    """Rename the app directory.
+
+    Args:
+        new_app_name: The new name for the app.
+        loglevel: The log level to use.
+
+    Raises:
+        Exit: If the command is not ran in the root dir or the app module cannot be imported.
+    """
+    # Set the log level.
+    console.set_log_level(loglevel)
+
+    if not constants.Config.FILE.exists():
+        console.error(
+            "No rxconfig.py found. Make sure you are in the root directory of your app."
+        )
+        raise typer.Exit(1)
+
+    sys.path.insert(0, str(Path.cwd()))
+
+    config = get_config()
+    module_path = importlib.util.find_spec(config.module)
+    if module_path is None:
+        console.error(f"Could not find module {config.module}.")
+        raise typer.Exit(1)
+
+    if not module_path.origin:
+        console.error(f"Could not find origin for module {config.module}.")
+        raise typer.Exit(1)
+    console.info(f"Renaming app directory to {new_app_name}.")
+    process_directory(
+        Path.cwd(),
+        config.app_name,
+        new_app_name,
+        exclude_dirs=[constants.Dirs.WEB, constants.Dirs.APP_ASSETS],
+    )
+
+    rename_path_up_tree(Path(module_path.origin), config.app_name, new_app_name)
+
+    console.success(f"App directory renamed to [bold]{new_app_name}[/bold].")
+
+
+def rename_imports_and_app_name(file_path: str | Path, old_name: str, new_name: str):
+    """Rename imports the file using string replacement as well as app_name in rxconfig.py.
+
+    Args:
+        file_path: The file to process.
+        old_name: The old name to replace.
+        new_name: The new name to use.
+    """
+    file_path = Path(file_path)
+    content = file_path.read_text()
+
+    # Replace `from old_name.` or `from old_name` with `from new_name`
+    content = re.sub(
+        rf"\bfrom {re.escape(old_name)}(\b|\.|\s)",
+        lambda match: f"from {new_name}{match.group(1)}",
+        content,
+    )
+
+    # Replace `import old_name` with `import new_name`
+    content = re.sub(
+        rf"\bimport {re.escape(old_name)}\b",
+        f"import {new_name}",
+        content,
+    )
+
+    # Replace `app_name="old_name"` in rx.Config
+    content = re.sub(
+        rf'\bapp_name\s*=\s*["\']{re.escape(old_name)}["\']',
+        f'app_name="{new_name}"',
+        content,
+    )
+
+    # Replace positional argument `"old_name"` in rx.Config
+    content = re.sub(
+        rf'\brx\.Config\(\s*["\']{re.escape(old_name)}["\']',
+        f'rx.Config("{new_name}"',
+        content,
+    )
+
+    file_path.write_text(content)
+
+
+def process_directory(
+    directory: str | Path,
+    old_name: str,
+    new_name: str,
+    exclude_dirs: list | None = None,
+    extensions: list | None = None,
+):
+    """Process files with specified extensions in a directory, excluding specified directories.
+
+    Args:
+        directory: The root directory to process.
+        old_name: The old name to replace.
+        new_name: The new name to use.
+        exclude_dirs: List of directory names to exclude. Defaults to None.
+        extensions: List of file extensions to process.
+    """
+    exclude_dirs = exclude_dirs or []
+    extensions = extensions or [
+        constants.Ext.PY,
+        constants.Ext.MD,
+    ]  # include .md files, typically used in reflex-web.
+    extensions_set = {ext.lstrip(".") for ext in extensions}
+    directory = Path(directory)
+
+    root_exclude_dirs = {directory / exclude_dir for exclude_dir in exclude_dirs}
+
+    files = (
+        p.resolve()
+        for p in directory.glob("**/*")
+        if p.is_file() and p.suffix.lstrip(".") in extensions_set
+    )
+
+    for file_path in files:
+        if not any(
+            file_path.is_relative_to(exclude_dir) for exclude_dir in root_exclude_dirs
+        ):
+            rename_imports_and_app_name(file_path, old_name, new_name)
+
+
 def create_config(app_name: str):
 def create_config(app_name: str):
     """Create a new rxconfig file.
     """Create a new rxconfig file.
 
 
@@ -921,6 +1083,7 @@ def install_bun():
             constants.Bun.INSTALL_URL,
             constants.Bun.INSTALL_URL,
             f"bun-v{constants.Bun.VERSION}",
             f"bun-v{constants.Bun.VERSION}",
             BUN_INSTALL=str(constants.Bun.ROOT_PATH),
             BUN_INSTALL=str(constants.Bun.ROOT_PATH),
+            BUN_VERSION=str(constants.Bun.VERSION),
         )
         )
 
 
 
 
@@ -1653,7 +1816,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
         generation_hash: The generation hash from reflex.build.
         generation_hash: The generation hash from reflex.build.
 
 
     Raises:
     Raises:
-        GeneratedCodeHasNoFunctionDefs: If the fetched code has no function definitions
+        GeneratedCodeHasNoFunctionDefsError: If the fetched code has no function definitions
             (the refactored reflex code is expected to have at least one root function defined).
             (the refactored reflex code is expected to have at least one root function defined).
     """
     """
     # Download the reflex code for the generation.
     # Download the reflex code for the generation.
@@ -1670,7 +1833,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
     # Determine the name of the last function, which renders the generated code.
     # Determine the name of the last function, which renders the generated code.
     defined_funcs = re.findall(r"def ([a-zA-Z_]+)\(", resp.text)
     defined_funcs = re.findall(r"def ([a-zA-Z_]+)\(", resp.text)
     if not defined_funcs:
     if not defined_funcs:
-        raise GeneratedCodeHasNoFunctionDefs(
+        raise GeneratedCodeHasNoFunctionDefsError(
             f"No function definitions found in generated code from {url!r}."
             f"No function definitions found in generated code from {url!r}."
         )
         )
     render_func_name = defined_funcs[-1]
     render_func_name = defined_funcs[-1]

+ 1 - 1
reflex/vars/base.py

@@ -1909,7 +1909,7 @@ def figure_out_type(value: Any) -> types.GenericType:
     return type(value)
     return type(value)
 
 
 
 
-class cached_property_no_lock(functools.cached_property):
+class cached_property_no_lock(functools.cached_property):  # noqa: N801
     """A special version of functools.cached_property that does not use a lock."""
     """A special version of functools.cached_property that does not use a lock."""
 
 
     def __init__(self, func):
     def __init__(self, func):

+ 2 - 2
reflex/vars/function.py

@@ -1802,13 +1802,13 @@ def _generate_overloads_for_function_var_call(maximum_args: int = 4) -> str:
                     )
                     )
                 ]
                 ]
                 function_type_hint = f"""FunctionVar[ReflexCallable[[{", ".join(required_params + optional_params)}], {return_type}]]"""
                 function_type_hint = f"""FunctionVar[ReflexCallable[[{", ".join(required_params + optional_params)}], {return_type}]]"""
-                NEWLINE = "\n"
+                new_line = "\n"
                 overloads.append(
                 overloads.append(
                     f"""
                     f"""
     @overload
     @overload
     def call(
     def call(
         self: {function_type_hint},
         self: {function_type_hint},
-        {"," + NEWLINE + "        ".join(required_args + optional_args)}
+        {"," + new_line + "        ".join(required_args + optional_args)}
     ) -> {return_type_var}: ...
     ) -> {return_type_var}: ...
     """
     """
                 )
                 )

+ 3 - 3
reflex/vars/number.py

@@ -22,7 +22,7 @@ from typing import (
 from typing_extensions import Unpack
 from typing_extensions import Unpack
 
 
 from reflex.constants.base import Dirs
 from reflex.constants.base import Dirs
-from reflex.utils.exceptions import PrimitiveUnserializableToJSON, VarTypeError
+from reflex.utils.exceptions import PrimitiveUnserializableToJSONError, VarTypeError
 from reflex.utils.imports import ImportDict, ImportVar
 from reflex.utils.imports import ImportDict, ImportVar
 
 
 from .base import (
 from .base import (
@@ -915,10 +915,10 @@ class LiteralNumberVar(LiteralVar, NumberVar):
             The JSON representation of the var.
             The JSON representation of the var.
 
 
         Raises:
         Raises:
-            PrimitiveUnserializableToJSON: If the var is unserializable to JSON.
+            PrimitiveUnserializableToJSONError: If the var is unserializable to JSON.
         """
         """
         if math.isinf(self._var_value) or math.isnan(self._var_value):
         if math.isinf(self._var_value) or math.isnan(self._var_value):
-            raise PrimitiveUnserializableToJSON(
+            raise PrimitiveUnserializableToJSONError(
                 f"No valid JSON representation for {self}"
                 f"No valid JSON representation for {self}"
             )
             )
         return json.dumps(self._var_value)
         return json.dumps(self._var_value)

+ 20 - 9
scripts/bun_install.sh

@@ -78,6 +78,14 @@ case $platform in
     ;;
     ;;
 esac
 esac
 
 
+case "$target" in
+'linux'*)
+    if [ -f /etc/alpine-release ]; then
+        target="$target-musl"
+    fi
+    ;;
+esac
+
 if [[ $target = darwin-x64 ]]; then
 if [[ $target = darwin-x64 ]]; then
     # Is this process running in Rosetta?
     # Is this process running in Rosetta?
     # redirect stderr to devnull to avoid error message when not running in Rosetta
     # redirect stderr to devnull to avoid error message when not running in Rosetta
@@ -91,19 +99,20 @@ GITHUB=${GITHUB-"https://github.com"}
 
 
 github_repo="$GITHUB/oven-sh/bun"
 github_repo="$GITHUB/oven-sh/bun"
 
 
-if [[ $target = darwin-x64 ]]; then
-    # If AVX2 isn't supported, use the -baseline build
+# If AVX2 isn't supported, use the -baseline build
+case "$target" in
+'darwin-x64'*)
     if [[ $(sysctl -a | grep machdep.cpu | grep AVX2) == '' ]]; then
     if [[ $(sysctl -a | grep machdep.cpu | grep AVX2) == '' ]]; then
-        target=darwin-x64-baseline
+        target="$target-baseline"
     fi
     fi
-fi
-
-if [[ $target = linux-x64 ]]; then
+    ;;
+'linux-x64'*)
     # If AVX2 isn't supported, use the -baseline build
     # If AVX2 isn't supported, use the -baseline build
     if [[ $(cat /proc/cpuinfo | grep avx2) = '' ]]; then
     if [[ $(cat /proc/cpuinfo | grep avx2) = '' ]]; then
-        target=linux-x64-baseline
+        target="$target-baseline"
     fi
     fi
-fi
+    ;;
+esac
 
 
 exe_name=bun
 exe_name=bun
 
 
@@ -113,8 +122,10 @@ if [[ $# = 2 && $2 = debug-info ]]; then
     info "You requested a debug build of bun. More information will be shown if a crash occurs."
     info "You requested a debug build of bun. More information will be shown if a crash occurs."
 fi
 fi
 
 
+bun_version=BUN_VERSION
+
 if [[ $# = 0 ]]; then
 if [[ $# = 0 ]]; then
-    bun_uri=$github_repo/releases/latest/download/bun-$target.zip
+    bun_uri=$github_repo/releases/download/bun-v$bun_version/bun-$target.zip
 else
 else
     bun_uri=$github_repo/releases/download/$1/bun-$target.zip
     bun_uri=$github_repo/releases/download/$1/bun-$target.zip
 fi
 fi

+ 4 - 0
scripts/install.ps1

@@ -214,8 +214,12 @@ function Install-Bun {
   # http://community.sqlbackupandftp.com/t/error-1073741515-solved/1305
   # http://community.sqlbackupandftp.com/t/error-1073741515-solved/1305
   if (($LASTEXITCODE -eq 3221225781) -or ($LASTEXITCODE -eq -1073741515)) # STATUS_DLL_NOT_FOUND
   if (($LASTEXITCODE -eq 3221225781) -or ($LASTEXITCODE -eq -1073741515)) # STATUS_DLL_NOT_FOUND
   { 
   { 
+    # TODO: as of July 2024, Bun has no external dependencies.
+    # I want to keep this error message in for a few months to ensure that
+    # if someone somehow runs into this, it can be reported.
     Write-Output "Install Failed - You are missing a DLL required to run bun.exe"
     Write-Output "Install Failed - You are missing a DLL required to run bun.exe"
     Write-Output "This can be solved by installing the Visual C++ Redistributable from Microsoft:`nSee https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist`nDirect Download -> https://aka.ms/vs/17/release/vc_redist.x64.exe`n`n"
     Write-Output "This can be solved by installing the Visual C++ Redistributable from Microsoft:`nSee https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist`nDirect Download -> https://aka.ms/vs/17/release/vc_redist.x64.exe`n`n"
+    Write-Output "The error above should be unreachable as Bun does not depend on this library. Please comment in https://github.com/oven-sh/bun/issues/8598 or open a new issue.`n`n"
     Write-Output "The command '${BunBin}\bun.exe --revision' exited with code ${LASTEXITCODE}`n"
     Write-Output "The command '${BunBin}\bun.exe --revision' exited with code ${LASTEXITCODE}`n"
     return 1
     return 1
   }
   }

+ 0 - 30
tests/integration/conftest.py

@@ -1,8 +1,6 @@
 """Shared conftest for all integration tests."""
 """Shared conftest for all integration tests."""
 
 
 import os
 import os
-import re
-from pathlib import Path
 
 
 import pytest
 import pytest
 
 
@@ -36,34 +34,6 @@ def xvfb():
         yield None
         yield None
 
 
 
 
-def pytest_exception_interact(node, call, report):
-    """Take and upload screenshot when tests fail.
-
-    Args:
-        node: The pytest item that failed.
-        call: The pytest call describing when/where the test was invoked.
-        report: The pytest log report object.
-    """
-    screenshot_dir = environment.SCREENSHOT_DIR.get()
-    if DISPLAY is None or screenshot_dir is None:
-        return
-
-    screenshot_dir = Path(screenshot_dir)
-    screenshot_dir.mkdir(parents=True, exist_ok=True)
-    safe_filename = re.sub(
-        r"(?u)[^-\w.]",
-        "_",
-        str(node.nodeid).strip().replace(" ", "_").replace(":", "_").replace(".py", ""),
-    )
-
-    try:
-        DISPLAY.waitgrab().save(
-            (Path(screenshot_dir) / safe_filename).with_suffix(".png"),
-        )
-    except Exception as e:
-        print(f"Failed to take screenshot for {node}: {e}")
-
-
 @pytest.fixture(
 @pytest.fixture(
     scope="session", params=[AppHarness, AppHarnessProd], ids=["dev", "prod"]
     scope="session", params=[AppHarness, AppHarnessProd], ids=["dev", "prod"]
 )
 )

+ 2 - 2
tests/integration/test_background_task.py

@@ -172,7 +172,7 @@ def BackgroundTask():
             rx.button("Reset", on_click=State.reset_counter, id="reset"),
             rx.button("Reset", on_click=State.reset_counter, id="reset"),
         )
         )
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     app.add_page(index)
     app.add_page(index)
 
 
 
 
@@ -288,7 +288,7 @@ def test_background_task(
     assert background_task._poll_for(lambda: counter.text == "620", timeout=40)
     assert background_task._poll_for(lambda: counter.text == "620", timeout=40)
     # all tasks should have exited and cleaned up
     # all tasks should have exited and cleaned up
     assert background_task._poll_for(
     assert background_task._poll_for(
-        lambda: not background_task.app_instance.background_tasks  # type: ignore
+        lambda: not background_task.app_instance._background_tasks  # type: ignore
     )
     )
 
 
 
 

+ 1 - 1
tests/integration/test_call_script.py

@@ -188,7 +188,7 @@ def CallScript():
             yield rx.call_script("inline_counter = 0; external_counter = 0")
             yield rx.call_script("inline_counter = 0; external_counter = 0")
             self.reset()
             self.reset()
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     Path("assets/external.js").write_text(external_scripts)
     Path("assets/external.js").write_text(external_scripts)
 
 
     @app.add_page
     @app.add_page

+ 3 - 1
tests/integration/test_client_storage.py

@@ -127,7 +127,7 @@ def ClientSide():
             rx.box(ClientSideSubSubState.s1s, id="s1s"),
             rx.box(ClientSideSubSubState.s1s, id="s1s"),
         )
         )
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     app.add_page(index)
     app.add_page(index)
     app.add_page(index, route="/foo")
     app.add_page(index, route="/foo")
 
 
@@ -321,6 +321,7 @@ async def test_client_side_state(
     assert not driver.get_cookies()
     assert not driver.get_cookies()
     local_storage_items = local_storage.items()
     local_storage_items = local_storage.items()
     local_storage_items.pop("last_compiled_time", None)
     local_storage_items.pop("last_compiled_time", None)
+    local_storage_items.pop("theme", None)
     assert not local_storage_items
     assert not local_storage_items
 
 
     # set some cookies and local storage values
     # set some cookies and local storage values
@@ -436,6 +437,7 @@ async def test_client_side_state(
 
 
     local_storage_items = local_storage.items()
     local_storage_items = local_storage.items()
     local_storage_items.pop("last_compiled_time", None)
     local_storage_items.pop("last_compiled_time", None)
+    local_storage_items.pop("theme", None)
     assert local_storage_items.pop(f"{sub_state_name}.l1") == "l1 value"
     assert local_storage_items.pop(f"{sub_state_name}.l1") == "l1 value"
     assert local_storage_items.pop(f"{sub_state_name}.l2") == "l2 value"
     assert local_storage_items.pop(f"{sub_state_name}.l2") == "l2 value"
     assert local_storage_items.pop("l3") == "l3 value"
     assert local_storage_items.pop("l3") == "l3 value"

+ 1 - 1
tests/integration/test_component_state.py

@@ -72,7 +72,7 @@ def ComponentStateApp():
             State=_Counter,
             State=_Counter,
         )
         )
 
 
-    app = rx.App(state=rx.State)  # noqa
+    app = rx.App(_state=rx.State)  # noqa
 
 
     @rx.page()
     @rx.page()
     def index():
     def index():

+ 1 - 1
tests/integration/test_connection_banner.py

@@ -36,7 +36,7 @@ def ConnectionBanner():
             rx.button("Delay", id="delay", on_click=State.delay),
             rx.button("Delay", id="delay", on_click=State.delay),
         )
         )
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     app.add_page(index)
     app.add_page(index)
 
 
 
 

+ 1 - 1
tests/integration/test_deploy_url.py

@@ -26,7 +26,7 @@ def DeployUrlSample() -> None:
             rx.button("GOTO SELF", on_click=State.goto_self, id="goto_self")
             rx.button("GOTO SELF", on_click=State.goto_self, id="goto_self")
         )
         )
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     app.add_page(index)
     app.add_page(index)
 
 
 
 

+ 1 - 1
tests/integration/test_dynamic_routes.py

@@ -138,7 +138,7 @@ def DynamicRoute():
     def redirect_page():
     def redirect_page():
         return rx.fragment(rx.text("redirecting..."))
         return rx.fragment(rx.text("redirecting..."))
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load)  # type: ignore
     app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load)  # type: ignore
     app.add_page(index, route="/static/x", on_load=DynamicState.on_load)  # type: ignore
     app.add_page(index, route="/static/x", on_load=DynamicState.on_load)  # type: ignore
     app.add_page(index)
     app.add_page(index)

+ 1 - 1
tests/integration/test_event_actions.py

@@ -158,7 +158,7 @@ def TestEventAction():
             on_click=EventActionState.on_click("outer"),  # type: ignore
             on_click=EventActionState.on_click("outer"),  # type: ignore
         )
         )
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     app.add_page(index)
     app.add_page(index)
 
 
 
 

+ 1 - 1
tests/integration/test_event_chain.py

@@ -144,7 +144,7 @@ def EventChain():
             time.sleep(0.5)
             time.sleep(0.5)
             self.interim_value = "final"
             self.interim_value = "final"
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
 
 
     token_input = rx.input(
     token_input = rx.input(
         value=State.router.session.client_token, is_read_only=True, id="token"
         value=State.router.session.client_token, is_read_only=True, id="token"

+ 1 - 1
tests/integration/test_exception_handlers.py

@@ -39,7 +39,7 @@ def TestApp():
             """
             """
             print(1 / number)
             print(1 / number)
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
 
 
     @app.add_page
     @app.add_page
     def index():
     def index():

+ 2 - 2
tests/integration/test_form_submit.py

@@ -30,7 +30,7 @@ def FormSubmit(form_component):
         def form_submit(self, form_data: Dict):
         def form_submit(self, form_data: Dict):
             self.form_data = form_data
             self.form_data = form_data
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
 
 
     @app.add_page
     @app.add_page
     def index():
     def index():
@@ -90,7 +90,7 @@ def FormSubmitName(form_component):
         def form_submit(self, form_data: Dict):
         def form_submit(self, form_data: Dict):
             self.form_data = form_data
             self.form_data = form_data
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
 
 
     @app.add_page
     @app.add_page
     def index():
     def index():

+ 1 - 1
tests/integration/test_input.py

@@ -16,7 +16,7 @@ def FullyControlledInput():
     class State(rx.State):
     class State(rx.State):
         text: str = "initial"
         text: str = "initial"
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
 
 
     @app.add_page
     @app.add_page
     def index():
     def index():

+ 1 - 1
tests/integration/test_login_flow.py

@@ -45,7 +45,7 @@ def LoginSample():
             rx.button("Do it", on_click=State.login, id="doit"),
             rx.button("Do it", on_click=State.login, id="doit"),
         )
         )
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     app.add_page(index)
     app.add_page(index)
     app.add_page(login)
     app.add_page(login)
 
 

+ 1 - 1
tests/integration/test_server_side_event.py

@@ -38,7 +38,7 @@ def ServerSideEvent():
         def set_value_return_c(self):
         def set_value_return_c(self):
             return rx.set_value("c", "")
             return rx.set_value("c", "")
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
 
 
     @app.add_page
     @app.add_page
     def index():
     def index():

+ 1 - 1
tests/integration/test_upload.py

@@ -166,7 +166,7 @@ def UploadFile():
             rx.text(UploadState.event_order.to_string(), id="event-order"),
             rx.text(UploadState.event_order.to_string(), id="event-order"),
         )
         )
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
     app.add_page(index)
     app.add_page(index)
 
 
 
 

+ 1 - 1
tests/integration/test_var_operations.py

@@ -41,7 +41,7 @@ def VarOperations():
         dict2: rx.Field[Dict[int, int]] = rx.field({3: 4})
         dict2: rx.Field[Dict[int, int]] = rx.field({3: 4})
         html_str: rx.Field[str] = rx.field("<div>hello</div>")
         html_str: rx.Field[str] = rx.field("<div>hello</div>")
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
 
 
     @rx.memo
     @rx.memo
     def memo_comp(list1: List[int], int_var1: int, id: str):
     def memo_comp(list1: List[int], int_var1: int, id: str):

+ 1 - 1
tests/integration/tests_playwright/test_datetime_operations.py

@@ -16,7 +16,7 @@ def DatetimeOperationsApp():
         date2: datetime = datetime(2031, 1, 1)
         date2: datetime = datetime(2031, 1, 1)
         date3: datetime = datetime(2021, 1, 1)
         date3: datetime = datetime(2021, 1, 1)
 
 
-    app = rx.App(state=DtOperationsState)
+    app = rx.App(_state=DtOperationsState)
 
 
     @app.add_page
     @app.add_page
     def index():
     def index():

+ 1 - 1
tests/integration/tests_playwright/test_table.py

@@ -20,7 +20,7 @@ def Table():
     """App using table component."""
     """App using table component."""
     import reflex as rx
     import reflex as rx
 
 
-    app = rx.App(state=rx.State)
+    app = rx.App(_state=rx.State)
 
 
     @app.add_page
     @app.add_page
     def index():
     def index():

+ 1 - 30
tests/units/conftest.py

@@ -1,11 +1,8 @@
 """Test fixtures."""
 """Test fixtures."""
 
 
 import asyncio
 import asyncio
-import contextlib
-import os
 import platform
 import platform
 import uuid
 import uuid
-from pathlib import Path
 from typing import Dict, Generator, Type
 from typing import Dict, Generator, Type
 from unittest import mock
 from unittest import mock
 
 
@@ -14,6 +11,7 @@ import pytest
 from reflex.app import App
 from reflex.app import App
 from reflex.event import EventSpec
 from reflex.event import EventSpec
 from reflex.model import ModelRegistry
 from reflex.model import ModelRegistry
+from reflex.testing import chdir
 from reflex.utils import prerequisites
 from reflex.utils import prerequisites
 
 
 from .states import (
 from .states import (
@@ -191,33 +189,6 @@ def router_data(router_data_headers) -> Dict[str, str]:
     }
     }
 
 
 
 
-# borrowed from py3.11
-class chdir(contextlib.AbstractContextManager):
-    """Non thread-safe context manager to change the current working directory."""
-
-    def __init__(self, path):
-        """Prepare contextmanager.
-
-        Args:
-            path: the path to change to
-        """
-        self.path = path
-        self._old_cwd = []
-
-    def __enter__(self):
-        """Save current directory and perform chdir."""
-        self._old_cwd.append(Path.cwd())
-        os.chdir(self.path)
-
-    def __exit__(self, *excinfo):
-        """Change back to previous directory on stack.
-
-        Args:
-            excinfo: sys.exc_info captured in the context block
-        """
-        os.chdir(self._old_cwd.pop())
-
-
 @pytest.fixture
 @pytest.fixture
 def tmp_working_dir(tmp_path):
 def tmp_working_dir(tmp_path):
     """Create a temporary directory and chdir to it.
     """Create a temporary directory and chdir to it.

+ 1 - 1
tests/units/middleware/test_hydrate_middleware.py

@@ -41,7 +41,7 @@ async def test_preprocess_no_events(hydrate_middleware, event1, mocker):
     mocker.patch("reflex.state.State.class_subclasses", {TestState})
     mocker.patch("reflex.state.State.class_subclasses", {TestState})
     state = State()
     state = State()
     update = await hydrate_middleware.preprocess(
     update = await hydrate_middleware.preprocess(
-        app=App(state=State),
+        app=App(_state=State),
         event=event1,
         event=event1,
         state=state,
         state=state,
     )
     )

+ 50 - 50
tests/units/test_app.py

@@ -235,14 +235,14 @@ def test_add_page_default_route(app: App, index_page, about_page):
         index_page: The index page.
         index_page: The index page.
         about_page: The about page.
         about_page: The about page.
     """
     """
-    assert app.pages == {}
-    assert app.unevaluated_pages == {}
+    assert app._pages == {}
+    assert app._unevaluated_pages == {}
     app.add_page(index_page)
     app.add_page(index_page)
     app._compile_page("index")
     app._compile_page("index")
-    assert app.pages.keys() == {"index"}
+    assert app._pages.keys() == {"index"}
     app.add_page(about_page)
     app.add_page(about_page)
     app._compile_page("about")
     app._compile_page("about")
-    assert app.pages.keys() == {"index", "about"}
+    assert app._pages.keys() == {"index", "about"}
 
 
 
 
 def test_add_page_set_route(app: App, index_page, windows_platform: bool):
 def test_add_page_set_route(app: App, index_page, windows_platform: bool):
@@ -254,10 +254,10 @@ def test_add_page_set_route(app: App, index_page, windows_platform: bool):
         windows_platform: Whether the system is windows.
         windows_platform: Whether the system is windows.
     """
     """
     route = "test" if windows_platform else "/test"
     route = "test" if windows_platform else "/test"
-    assert app.unevaluated_pages == {}
+    assert app._unevaluated_pages == {}
     app.add_page(index_page, route=route)
     app.add_page(index_page, route=route)
     app._compile_page("test")
     app._compile_page("test")
-    assert app.pages.keys() == {"test"}
+    assert app._pages.keys() == {"test"}
 
 
 
 
 def test_add_page_set_route_dynamic(index_page, windows_platform: bool):
 def test_add_page_set_route_dynamic(index_page, windows_platform: bool):
@@ -267,18 +267,18 @@ def test_add_page_set_route_dynamic(index_page, windows_platform: bool):
         index_page: The index page.
         index_page: The index page.
         windows_platform: Whether the system is windows.
         windows_platform: Whether the system is windows.
     """
     """
-    app = App(state=EmptyState)
-    assert app.state is not None
+    app = App(_state=EmptyState)
+    assert app._state is not None
     route = "/test/[dynamic]"
     route = "/test/[dynamic]"
-    assert app.unevaluated_pages == {}
+    assert app._unevaluated_pages == {}
     app.add_page(index_page, route=route)
     app.add_page(index_page, route=route)
     app._compile_page("test/[dynamic]")
     app._compile_page("test/[dynamic]")
-    assert app.pages.keys() == {"test/[dynamic]"}
-    assert "dynamic" in app.state.computed_vars
-    assert app.state.computed_vars["dynamic"]._deps(objclass=EmptyState) == {
+    assert app._pages.keys() == {"test/[dynamic]"}
+    assert "dynamic" in app._state.computed_vars
+    assert app._state.computed_vars["dynamic"]._deps(objclass=EmptyState) == {
         constants.ROUTER
         constants.ROUTER
     }
     }
-    assert constants.ROUTER in app.state()._computed_var_dependencies
+    assert constants.ROUTER in app._state()._computed_var_dependencies
 
 
 
 
 def test_add_page_set_route_nested(app: App, index_page, windows_platform: bool):
 def test_add_page_set_route_nested(app: App, index_page, windows_platform: bool):
@@ -290,9 +290,9 @@ def test_add_page_set_route_nested(app: App, index_page, windows_platform: bool)
         windows_platform: Whether the system is windows.
         windows_platform: Whether the system is windows.
     """
     """
     route = "test\\nested" if windows_platform else "/test/nested"
     route = "test\\nested" if windows_platform else "/test/nested"
-    assert app.unevaluated_pages == {}
+    assert app._unevaluated_pages == {}
     app.add_page(index_page, route=route)
     app.add_page(index_page, route=route)
-    assert app.unevaluated_pages.keys() == {route.strip(os.path.sep)}
+    assert app._unevaluated_pages.keys() == {route.strip(os.path.sep)}
 
 
 
 
 def test_add_page_invalid_api_route(app: App, index_page):
 def test_add_page_invalid_api_route(app: App, index_page):
@@ -412,8 +412,8 @@ async def test_initialize_with_state(test_state: Type[ATestState], token: str):
         test_state: The default state.
         test_state: The default state.
         token: a Token.
         token: a Token.
     """
     """
-    app = App(state=test_state)
-    assert app.state == test_state
+    app = App(_state=test_state)
+    assert app._state == test_state
 
 
     # Get a state for a given token.
     # Get a state for a given token.
     state = await app.state_manager.get_state(_substate_key(token, test_state))
     state = await app.state_manager.get_state(_substate_key(token, test_state))
@@ -431,7 +431,7 @@ async def test_set_and_get_state(test_state):
     Args:
     Args:
         test_state: The default state.
         test_state: The default state.
     """
     """
-    app = App(state=test_state)
+    app = App(_state=test_state)
 
 
     # Create two tokens.
     # Create two tokens.
     token1 = str(uuid.uuid4()) + f"_{test_state.get_full_name()}"
     token1 = str(uuid.uuid4()) + f"_{test_state.get_full_name()}"
@@ -826,7 +826,7 @@ async def test_upload_file_without_annotation(state, tmp_path, token):
         token: a Token.
         token: a Token.
     """
     """
     state._tmp_path = tmp_path
     state._tmp_path = tmp_path
-    app = App(state=State)
+    app = App(_state=State)
 
 
     request_mock = unittest.mock.Mock()
     request_mock = unittest.mock.Mock()
     request_mock.headers = {
     request_mock.headers = {
@@ -860,7 +860,7 @@ async def test_upload_file_background(state, tmp_path, token):
         token: a Token.
         token: a Token.
     """
     """
     state._tmp_path = tmp_path
     state._tmp_path = tmp_path
-    app = App(state=State)
+    app = App(_state=State)
 
 
     request_mock = unittest.mock.Mock()
     request_mock = unittest.mock.Mock()
     request_mock.headers = {
     request_mock.headers = {
@@ -937,8 +937,8 @@ def test_dynamic_arg_shadow(
     """
     """
     arg_name = "counter"
     arg_name = "counter"
     route = f"/test/[{arg_name}]"
     route = f"/test/[{arg_name}]"
-    app = app_module_mock.app = App(state=DynamicState)
-    assert app.state is not None
+    app = app_module_mock.app = App(_state=DynamicState)
+    assert app._state is not None
     with pytest.raises(NameError):
     with pytest.raises(NameError):
         app.add_page(index_page, route=route, on_load=DynamicState.on_load)  # type: ignore
         app.add_page(index_page, route=route, on_load=DynamicState.on_load)  # type: ignore
 
 
@@ -962,7 +962,7 @@ def test_multiple_dynamic_args(
     arg_name = "my_arg"
     arg_name = "my_arg"
     route = f"/test/[{arg_name}]"
     route = f"/test/[{arg_name}]"
     route2 = f"/test2/[{arg_name}]"
     route2 = f"/test2/[{arg_name}]"
-    app = app_module_mock.app = App(state=EmptyState)
+    app = app_module_mock.app = App(_state=EmptyState)
     app.add_page(index_page, route=route)
     app.add_page(index_page, route=route)
     app.add_page(index_page, route=route2)
     app.add_page(index_page, route=route2)
 
 
@@ -989,16 +989,16 @@ async def test_dynamic_route_var_route_change_completed_on_load(
     """
     """
     arg_name = "dynamic"
     arg_name = "dynamic"
     route = f"/test/[{arg_name}]"
     route = f"/test/[{arg_name}]"
-    app = app_module_mock.app = App(state=DynamicState)
-    assert app.state is not None
-    assert arg_name not in app.state.vars
+    app = app_module_mock.app = App(_state=DynamicState)
+    assert app._state is not None
+    assert arg_name not in app._state.vars
     app.add_page(index_page, route=route, on_load=DynamicState.on_load)  # type: ignore
     app.add_page(index_page, route=route, on_load=DynamicState.on_load)  # type: ignore
-    assert arg_name in app.state.vars
-    assert arg_name in app.state.computed_vars
-    assert app.state.computed_vars[arg_name]._deps(objclass=DynamicState) == {
+    assert arg_name in app._state.vars
+    assert arg_name in app._state.computed_vars
+    assert app._state.computed_vars[arg_name]._deps(objclass=DynamicState) == {
         constants.ROUTER
         constants.ROUTER
     }
     }
-    assert constants.ROUTER in app.state()._computed_var_dependencies
+    assert constants.ROUTER in app._state()._computed_var_dependencies
 
 
     substate_token = _substate_key(token, DynamicState)
     substate_token = _substate_key(token, DynamicState)
     sid = "mock_sid"
     sid = "mock_sid"
@@ -1173,7 +1173,7 @@ async def test_process_events(mocker, token: str):
         "headers": {},
         "headers": {},
         "ip": "127.0.0.1",
         "ip": "127.0.0.1",
     }
     }
-    app = App(state=GenState)
+    app = App(_state=GenState)
 
 
     mocker.patch.object(app, "_postprocess", AsyncMock())
     mocker.patch.object(app, "_postprocess", AsyncMock())
     event = Event(
     event = Event(
@@ -1219,7 +1219,7 @@ def test_overlay_component(
         overlay_component: The overlay_component to pass to App.
         overlay_component: The overlay_component to pass to App.
         exp_page_child: The type of the expected child in the page fragment.
         exp_page_child: The type of the expected child in the page fragment.
     """
     """
-    app = App(state=state, overlay_component=overlay_component)
+    app = App(_state=state, overlay_component=overlay_component)
     app._setup_overlay_component()
     app._setup_overlay_component()
     if exp_page_child is None:
     if exp_page_child is None:
         assert app.overlay_component is None
         assert app.overlay_component is None
@@ -1238,7 +1238,7 @@ def test_overlay_component(
     # overlay components are wrapped during compile only
     # overlay components are wrapped during compile only
     app._compile_page("test")
     app._compile_page("test")
     app._setup_overlay_component()
     app._setup_overlay_component()
-    page = app.pages["test"]
+    page = app._pages["test"]
 
 
     if exp_page_child is not None:
     if exp_page_child is not None:
         assert len(page.children) == 3
         assert len(page.children) == 3
@@ -1356,52 +1356,52 @@ def test_app_wrap_priority(compilable_app: tuple[App, Path]):
 def test_app_state_determination():
 def test_app_state_determination():
     """Test that the stateless status of an app is determined correctly."""
     """Test that the stateless status of an app is determined correctly."""
     a1 = App()
     a1 = App()
-    assert a1.state is None
+    assert a1._state is None
 
 
     # No state, no router, no event handlers.
     # No state, no router, no event handlers.
     a1.add_page(rx.box("Index"), route="/")
     a1.add_page(rx.box("Index"), route="/")
-    assert a1.state is None
+    assert a1._state is None
 
 
     # Add a page with `on_load` enables state.
     # Add a page with `on_load` enables state.
     a1.add_page(rx.box("About"), route="/about", on_load=rx.console_log(""))
     a1.add_page(rx.box("About"), route="/about", on_load=rx.console_log(""))
     a1._compile_page("about")
     a1._compile_page("about")
-    assert a1.state is not None
+    assert a1._state is not None
 
 
     a2 = App()
     a2 = App()
-    assert a2.state is None
+    assert a2._state is None
 
 
     # Referencing a state Var enables state.
     # Referencing a state Var enables state.
     a2.add_page(rx.box(rx.text(GenState.value)), route="/")
     a2.add_page(rx.box(rx.text(GenState.value)), route="/")
     a2._compile_page("index")
     a2._compile_page("index")
-    assert a2.state is not None
+    assert a2._state is not None
 
 
     a3 = App()
     a3 = App()
-    assert a3.state is None
+    assert a3._state is None
 
 
     # Referencing router enables state.
     # Referencing router enables state.
     a3.add_page(rx.box(rx.text(State.router.page.full_path)), route="/")
     a3.add_page(rx.box(rx.text(State.router.page.full_path)), route="/")
     a3._compile_page("index")
     a3._compile_page("index")
-    assert a3.state is not None
+    assert a3._state is not None
 
 
     a4 = App()
     a4 = App()
-    assert a4.state is None
+    assert a4._state is None
 
 
     a4.add_page(rx.box(rx.button("Click", on_click=rx.console_log(""))), route="/")
     a4.add_page(rx.box(rx.button("Click", on_click=rx.console_log(""))), route="/")
-    assert a4.state is None
+    assert a4._state is None
 
 
     a4.add_page(
     a4.add_page(
         rx.box(rx.button("Click", on_click=DynamicState.on_counter)), route="/page2"
         rx.box(rx.button("Click", on_click=DynamicState.on_counter)), route="/page2"
     )
     )
     a4._compile_page("page2")
     a4._compile_page("page2")
-    assert a4.state is not None
+    assert a4._state is not None
 
 
 
 
 def test_raise_on_state():
 def test_raise_on_state():
     """Test that the state is set."""
     """Test that the state is set."""
     # state kwargs is deprecated, we just make sure the app is created anyway.
     # state kwargs is deprecated, we just make sure the app is created anyway.
-    _app = App(state=State)
-    assert _app.state is not None
-    assert issubclass(_app.state, State)
+    _app = App(_state=State)
+    assert _app._state is not None
+    assert issubclass(_app._state, State)
 
 
 
 
 def test_call_app():
 def test_call_app():
@@ -1468,7 +1468,7 @@ def test_add_page_component_returning_tuple():
     app._compile_page("index")
     app._compile_page("index")
     app._compile_page("page2")
     app._compile_page("page2")
 
 
-    fragment_wrapper = app.pages["index"].children[0]
+    fragment_wrapper = app._pages["index"].children[0]
     assert isinstance(fragment_wrapper, Fragment)
     assert isinstance(fragment_wrapper, Fragment)
     first_text = fragment_wrapper.children[0]
     first_text = fragment_wrapper.children[0]
     assert isinstance(first_text, Text)
     assert isinstance(first_text, Text)
@@ -1478,7 +1478,7 @@ def test_add_page_component_returning_tuple():
     assert str(second_text.children[0].contents) == '"second"'  # type: ignore
     assert str(second_text.children[0].contents) == '"second"'  # type: ignore
 
 
     # Test page with trailing comma.
     # Test page with trailing comma.
-    page2_fragment_wrapper = app.pages["page2"].children[0]
+    page2_fragment_wrapper = app._pages["page2"].children[0]
     assert isinstance(page2_fragment_wrapper, Fragment)
     assert isinstance(page2_fragment_wrapper, Fragment)
     third_text = page2_fragment_wrapper.children[0]
     third_text = page2_fragment_wrapper.children[0]
     assert isinstance(third_text, Text)
     assert isinstance(third_text, Text)
@@ -1552,7 +1552,7 @@ def test_app_with_valid_var_dependencies(compilable_app: tuple[App, Path]):
         def bar(self) -> str:
         def bar(self) -> str:
             return "bar"
             return "bar"
 
 
-    app.state = ValidDepState
+    app._state = ValidDepState
     app._compile()
     app._compile()
 
 
 
 
@@ -1564,7 +1564,7 @@ def test_app_with_invalid_var_dependencies(compilable_app: tuple[App, Path]):
         def bar(self) -> str:
         def bar(self) -> str:
             return "bar"
             return "bar"
 
 
-    app.state = InvalidDepState
+    app._state = InvalidDepState
     with pytest.raises(exceptions.VarDependencyError):
     with pytest.raises(exceptions.VarDependencyError):
         app._compile()
         app._compile()
 
 

+ 161 - 0
tests/units/test_prerequisites.py

@@ -1,20 +1,28 @@
 import json
 import json
 import re
 import re
+import shutil
 import tempfile
 import tempfile
+from pathlib import Path
 from unittest.mock import Mock, mock_open
 from unittest.mock import Mock, mock_open
 
 
 import pytest
 import pytest
+from typer.testing import CliRunner
 
 
 from reflex import constants
 from reflex import constants
 from reflex.config import Config
 from reflex.config import Config
+from reflex.reflex import cli
+from reflex.testing import chdir
 from reflex.utils.prerequisites import (
 from reflex.utils.prerequisites import (
     CpuInfo,
     CpuInfo,
     _update_next_config,
     _update_next_config,
     cached_procedure,
     cached_procedure,
     get_cpu_info,
     get_cpu_info,
     initialize_requirements_txt,
     initialize_requirements_txt,
+    rename_imports_and_app_name,
 )
 )
 
 
+runner = CliRunner()
+
 
 
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(
     "config, export, expected_output",
     "config, export, expected_output",
@@ -224,3 +232,156 @@ def test_get_cpu_info():
     for attr in ("manufacturer_id", "model_name", "address_width"):
     for attr in ("manufacturer_id", "model_name", "address_width"):
         value = getattr(cpu_info, attr)
         value = getattr(cpu_info, attr)
         assert value.strip() if attr != "address_width" else value
         assert value.strip() if attr != "address_width" else value
+
+
+@pytest.fixture
+def temp_directory():
+    temp_dir = tempfile.mkdtemp()
+    yield Path(temp_dir)
+    shutil.rmtree(temp_dir)
+
+
+@pytest.mark.parametrize(
+    "config_code,expected",
+    [
+        ("rx.Config(app_name='old_name')", 'rx.Config(app_name="new_name")'),
+        ('rx.Config(app_name="old_name")', 'rx.Config(app_name="new_name")'),
+        ("rx.Config('old_name')", 'rx.Config("new_name")'),
+        ('rx.Config("old_name")', 'rx.Config("new_name")'),
+    ],
+)
+def test_rename_imports_and_app_name(temp_directory, config_code, expected):
+    file_path = temp_directory / "rxconfig.py"
+    content = f"""
+config = {config_code}
+"""
+    file_path.write_text(content)
+
+    rename_imports_and_app_name(file_path, "old_name", "new_name")
+
+    updated_content = file_path.read_text()
+    expected_content = f"""
+config = {expected}
+"""
+    assert updated_content == expected_content
+
+
+def test_regex_edge_cases(temp_directory):
+    file_path = temp_directory / "example.py"
+    content = """
+from old_name.module import something
+import old_name
+from old_name import something_else as alias
+from old_name
+"""
+    file_path.write_text(content)
+
+    rename_imports_and_app_name(file_path, "old_name", "new_name")
+
+    updated_content = file_path.read_text()
+    expected_content = """
+from new_name.module import something
+import new_name
+from new_name import something_else as alias
+from new_name
+"""
+    assert updated_content == expected_content
+
+
+def test_cli_rename_command(temp_directory):
+    foo_dir = temp_directory / "foo"
+    foo_dir.mkdir()
+    (foo_dir / "__init__").touch()
+    (foo_dir / ".web").mkdir()
+    (foo_dir / "assets").mkdir()
+    (foo_dir / "foo").mkdir()
+    (foo_dir / "foo" / "__init__.py").touch()
+    (foo_dir / "rxconfig.py").touch()
+    (foo_dir / "rxconfig.py").write_text(
+        """
+import reflex as rx
+
+config = rx.Config(
+    app_name="foo",
+)
+"""
+    )
+    (foo_dir / "foo" / "components").mkdir()
+    (foo_dir / "foo" / "components" / "__init__.py").touch()
+    (foo_dir / "foo" / "components" / "base.py").touch()
+    (foo_dir / "foo" / "components" / "views.py").touch()
+    (foo_dir / "foo" / "components" / "base.py").write_text(
+        """
+import reflex as rx
+from foo.components import views
+from foo.components.views import *
+from .base import *
+
+def random_component():
+    return rx.fragment()
+"""
+    )
+    (foo_dir / "foo" / "foo.py").touch()
+    (foo_dir / "foo" / "foo.py").write_text(
+        """
+import reflex as rx
+import foo.components.base
+from foo.components.base import random_component
+
+class State(rx.State):
+  pass
+
+
+def index():
+   return rx.text("Hello, World!")
+
+app = rx.App()
+app.add_page(index)
+"""
+    )
+
+    with chdir(temp_directory / "foo"):
+        result = runner.invoke(cli, ["rename", "bar"])
+
+    assert result.exit_code == 0
+    assert (foo_dir / "rxconfig.py").read_text() == (
+        """
+import reflex as rx
+
+config = rx.Config(
+    app_name="bar",
+)
+"""
+    )
+    assert (foo_dir / "bar").exists()
+    assert not (foo_dir / "foo").exists()
+    assert (foo_dir / "bar" / "components" / "base.py").read_text() == (
+        """
+import reflex as rx
+from bar.components import views
+from bar.components.views import *
+from .base import *
+
+def random_component():
+    return rx.fragment()
+"""
+    )
+    assert (foo_dir / "bar" / "bar.py").exists()
+    assert not (foo_dir / "bar" / "foo.py").exists()
+    assert (foo_dir / "bar" / "bar.py").read_text() == (
+        """
+import reflex as rx
+import bar.components.base
+from bar.components.base import random_component
+
+class State(rx.State):
+  pass
+
+
+def index():
+   return rx.text("Hello, World!")
+
+app = rx.App()
+app.add_page(index)
+"""
+    )

+ 2 - 2
tests/units/test_route.py

@@ -89,7 +89,7 @@ def app():
     ],
     ],
 )
 )
 def test_check_routes_conflict_invalid(mocker, app, route1, route2):
 def test_check_routes_conflict_invalid(mocker, app, route1, route2):
-    mocker.patch.object(app, "pages", {route1: []})
+    mocker.patch.object(app, "_pages", {route1: []})
     with pytest.raises(ValueError):
     with pytest.raises(ValueError):
         app._check_routes_conflict(route2)
         app._check_routes_conflict(route2)
 
 
@@ -117,6 +117,6 @@ def test_check_routes_conflict_invalid(mocker, app, route1, route2):
     ],
     ],
 )
 )
 def test_check_routes_conflict_valid(mocker, app, route1, route2):
 def test_check_routes_conflict_valid(mocker, app, route1, route2):
-    mocker.patch.object(app, "pages", {route1: []})
+    mocker.patch.object(app, "_pages", {route1: []})
     # test that running this does not throw an error.
     # test that running this does not throw an error.
     app._check_routes_conflict(route2)
     app._check_routes_conflict(route2)

+ 14 - 14
tests/units/test_state.py

@@ -1912,12 +1912,12 @@ def mock_app_simple(monkeypatch) -> rx.App:
     Returns:
     Returns:
         The app, after mocking out prerequisites.get_app()
         The app, after mocking out prerequisites.get_app()
     """
     """
-    app = App(state=TestState)
+    app = App(_state=TestState)
 
 
     app_module = Mock()
     app_module = Mock()
 
 
     setattr(app_module, CompileVars.APP, app)
     setattr(app_module, CompileVars.APP, app)
-    app.state = TestState
+    app._state = TestState
     app.event_namespace.emit = CopyingAsyncMock()  # type: ignore
     app.event_namespace.emit = CopyingAsyncMock()  # type: ignore
 
 
     def _mock_get_app(*args, **kwargs):
     def _mock_get_app(*args, **kwargs):
@@ -2161,7 +2161,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str):
         token: A token.
         token: A token.
     """
     """
     router_data = {"query": {}}
     router_data = {"query": {}}
-    mock_app.state_manager.state = mock_app.state = BackgroundTaskState
+    mock_app.state_manager.state = mock_app._state = BackgroundTaskState
     async for update in rx.app.process(  # type: ignore
     async for update in rx.app.process(  # type: ignore
         mock_app,
         mock_app,
         Event(
         Event(
@@ -2179,7 +2179,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str):
 
 
     # wait for the coroutine to start
     # wait for the coroutine to start
     await asyncio.sleep(0.5 if CI else 0.1)
     await asyncio.sleep(0.5 if CI else 0.1)
-    assert len(mock_app.background_tasks) == 1
+    assert len(mock_app._background_tasks) == 1
 
 
     # Process another normal event
     # Process another normal event
     async for update in rx.app.process(  # type: ignore
     async for update in rx.app.process(  # type: ignore
@@ -2211,9 +2211,9 @@ async def test_background_task_no_block(mock_app: rx.App, token: str):
         )
         )
 
 
     # Explicit wait for background tasks
     # Explicit wait for background tasks
-    for task in tuple(mock_app.background_tasks):
+    for task in tuple(mock_app._background_tasks):
         await task
         await task
-    assert not mock_app.background_tasks
+    assert not mock_app._background_tasks
 
 
     exp_order = [
     exp_order = [
         "background_task:start",
         "background_task:start",
@@ -2292,7 +2292,7 @@ async def test_background_task_reset(mock_app: rx.App, token: str):
         token: A token.
         token: A token.
     """
     """
     router_data = {"query": {}}
     router_data = {"query": {}}
-    mock_app.state_manager.state = mock_app.state = BackgroundTaskState
+    mock_app.state_manager.state = mock_app._state = BackgroundTaskState
     async for update in rx.app.process(  # type: ignore
     async for update in rx.app.process(  # type: ignore
         mock_app,
         mock_app,
         Event(
         Event(
@@ -2309,9 +2309,9 @@ async def test_background_task_reset(mock_app: rx.App, token: str):
         assert update == StateUpdate()
         assert update == StateUpdate()
 
 
     # Explicit wait for background tasks
     # Explicit wait for background tasks
-    for task in tuple(mock_app.background_tasks):
+    for task in tuple(mock_app._background_tasks):
         await task
         await task
-    assert not mock_app.background_tasks
+    assert not mock_app._background_tasks
 
 
     assert (
     assert (
         await mock_app.state_manager.get_state(
         await mock_app.state_manager.get_state(
@@ -2896,7 +2896,7 @@ async def test_preprocess(app_module_mock, token, test_state, expected, mocker):
         "reflex.state.State.class_subclasses", {test_state, OnLoadInternalState}
         "reflex.state.State.class_subclasses", {test_state, OnLoadInternalState}
     )
     )
     app = app_module_mock.app = App(
     app = app_module_mock.app = App(
-        state=State, load_events={"index": [test_state.test_handler]}
+        _state=State, _load_events={"index": [test_state.test_handler]}
     )
     )
     async with app.state_manager.modify_state(_substate_key(token, State)) as state:
     async with app.state_manager.modify_state(_substate_key(token, State)) as state:
         state.router_data = {"simulate": "hydrate"}
         state.router_data = {"simulate": "hydrate"}
@@ -2943,8 +2943,8 @@ async def test_preprocess_multiple_load_events(app_module_mock, token, mocker):
         "reflex.state.State.class_subclasses", {OnLoadState, OnLoadInternalState}
         "reflex.state.State.class_subclasses", {OnLoadState, OnLoadInternalState}
     )
     )
     app = app_module_mock.app = App(
     app = app_module_mock.app = App(
-        state=State,
-        load_events={"index": [OnLoadState.test_handler, OnLoadState.test_handler]},
+        _state=State,
+        _load_events={"index": [OnLoadState.test_handler, OnLoadState.test_handler]},
     )
     )
     async with app.state_manager.modify_state(_substate_key(token, State)) as state:
     async with app.state_manager.modify_state(_substate_key(token, State)) as state:
         state.router_data = {"simulate": "hydrate"}
         state.router_data = {"simulate": "hydrate"}
@@ -2989,7 +2989,7 @@ async def test_get_state(mock_app: rx.App, token: str):
         mock_app: An app that will be returned by `get_app()`
         mock_app: An app that will be returned by `get_app()`
         token: A token.
         token: A token.
     """
     """
-    mock_app.state_manager.state = mock_app.state = TestState
+    mock_app.state_manager.state = mock_app._state = TestState
 
 
     # Get instance of ChildState2.
     # Get instance of ChildState2.
     test_state = await mock_app.state_manager.get_state(
     test_state = await mock_app.state_manager.get_state(
@@ -3159,7 +3159,7 @@ async def test_get_state_from_sibling_not_cached(mock_app: rx.App, token: str):
 
 
         pass
         pass
 
 
-    mock_app.state_manager.state = mock_app.state = Parent
+    mock_app.state_manager.state = mock_app._state = Parent
 
 
     # Get the top level state via unconnected sibling.
     # Get the top level state via unconnected sibling.
     root = await mock_app.state_manager.get_state(_substate_key(token, Child))
     root = await mock_app.state_manager.get_state(_substate_key(token, Child))

+ 1 - 1
tests/units/test_state_tree.py

@@ -222,7 +222,7 @@ async def state_manager_redis(
     Yields:
     Yields:
         A state manager instance
         A state manager instance
     """
     """
-    app_module_mock.app = rx.App(state=Root)
+    app_module_mock.app = rx.App(_state=Root)
     state_manager = app_module_mock.app.state_manager
     state_manager = app_module_mock.app.state_manager
 
 
     if not isinstance(state_manager, StateManagerRedis):
     if not isinstance(state_manager, StateManagerRedis):

+ 1 - 1
tests/units/test_testing.py

@@ -23,7 +23,7 @@ def test_app_harness(tmp_path):
         class State(rx.State):
         class State(rx.State):
             pass
             pass
 
 
-        app = rx.App(state=State)
+        app = rx.App(_state=State)
         app.add_page(lambda: rx.text("Basic App"), route="/", title="index")
         app.add_page(lambda: rx.text("Basic App"), route="/", title="index")
         app._compile()
         app._compile()
 
 

+ 2 - 2
tests/units/test_var.py

@@ -12,7 +12,7 @@ from reflex.base import Base
 from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
 from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
 from reflex.state import BaseState
 from reflex.state import BaseState
 from reflex.utils.exceptions import (
 from reflex.utils.exceptions import (
-    PrimitiveUnserializableToJSON,
+    PrimitiveUnserializableToJSONError,
     UntypedComputedVarError,
     UntypedComputedVarError,
 )
 )
 from reflex.utils.imports import ImportVar
 from reflex.utils.imports import ImportVar
@@ -1234,7 +1234,7 @@ def test_inf_and_nan(var, expected_js):
     assert str(var) == expected_js
     assert str(var) == expected_js
     assert isinstance(var, NumberVar)
     assert isinstance(var, NumberVar)
     assert isinstance(var, LiteralVar)
     assert isinstance(var, LiteralVar)
-    with pytest.raises(PrimitiveUnserializableToJSON):
+    with pytest.raises(PrimitiveUnserializableToJSONError):
         var.json()
         var.json()