浏览代码

Merge branch 'main' into lendemor/add_custom_palette_function

Lendemor 8 月之前
父节点
当前提交
be58a4c17e
共有 83 个文件被更改,包括 1748 次插入3942 次删除
  1. 1 0
      .gitignore
  2. 20 123
      docker-example/README.md
  3. 5 0
      docker-example/production-app-platform/.dockerignore
  4. 65 0
      docker-example/production-app-platform/Dockerfile
  5. 113 0
      docker-example/production-app-platform/README.md
  6. 0 0
      docker-example/production-compose/.dockerignore
  7. 0 0
      docker-example/production-compose/Caddy.Dockerfile
  8. 0 0
      docker-example/production-compose/Caddyfile
  9. 3 2
      docker-example/production-compose/Dockerfile
  10. 75 0
      docker-example/production-compose/README.md
  11. 0 0
      docker-example/production-compose/compose.prod.yaml
  12. 0 0
      docker-example/production-compose/compose.tools.yaml
  13. 0 1
      docker-example/production-compose/compose.yaml
  14. 0 1
      docker-example/requirements.txt
  15. 5 0
      docker-example/simple-one-port/.dockerignore
  16. 14 0
      docker-example/simple-one-port/Caddyfile
  17. 6 22
      docker-example/simple-one-port/Dockerfile
  18. 36 0
      docker-example/simple-one-port/README.md
  19. 5 0
      docker-example/simple-two-port/.dockerignore
  20. 7 2
      docker-example/simple-two-port/Dockerfile
  21. 44 0
      docker-example/simple-two-port/README.md
  22. 39 0
      integration/test_background_task.py
  23. 18 0
      integration/test_var_operations.py
  24. 0 4
      reflex/.templates/apps/demo/.gitignore
  25. 二进制
      reflex/.templates/apps/demo/assets/favicon.ico
  26. 0 10
      reflex/.templates/apps/demo/assets/github.svg
  27. 0 37
      reflex/.templates/apps/demo/assets/icon.svg
  28. 0 68
      reflex/.templates/apps/demo/assets/logo.svg
  29. 0 13
      reflex/.templates/apps/demo/assets/paneleft.svg
  30. 0 1
      reflex/.templates/apps/demo/code/__init__.py
  31. 0 127
      reflex/.templates/apps/demo/code/demo.py
  32. 0 7
      reflex/.templates/apps/demo/code/pages/__init__.py
  33. 0 31
      reflex/.templates/apps/demo/code/pages/chatapp.py
  34. 0 360
      reflex/.templates/apps/demo/code/pages/datatable.py
  35. 0 257
      reflex/.templates/apps/demo/code/pages/forms.py
  36. 0 253
      reflex/.templates/apps/demo/code/pages/graphing.py
  37. 0 56
      reflex/.templates/apps/demo/code/pages/home.py
  38. 0 178
      reflex/.templates/apps/demo/code/sidebar.py
  39. 0 22
      reflex/.templates/apps/demo/code/state.py
  40. 0 40
      reflex/.templates/apps/demo/code/states/form_state.py
  41. 0 47
      reflex/.templates/apps/demo/code/states/pie_state.py
  42. 0 68
      reflex/.templates/apps/demo/code/styles.py
  43. 0 0
      reflex/.templates/apps/demo/code/webui/__init__.py
  44. 0 4
      reflex/.templates/apps/demo/code/webui/components/__init__.py
  45. 0 118
      reflex/.templates/apps/demo/code/webui/components/chat.py
  46. 0 19
      reflex/.templates/apps/demo/code/webui/components/loading_icon.py
  47. 0 56
      reflex/.templates/apps/demo/code/webui/components/modal.py
  48. 0 70
      reflex/.templates/apps/demo/code/webui/components/navbar.py
  49. 0 66
      reflex/.templates/apps/demo/code/webui/components/sidebar.py
  50. 0 146
      reflex/.templates/apps/demo/code/webui/state.py
  51. 0 88
      reflex/.templates/apps/demo/code/webui/styles.py
  52. 2 2
      reflex/.templates/jinja/web/pages/utils.js.jinja2
  53. 1 1
      reflex/__init__.py
  54. 1 1
      reflex/__init__.pyi
  55. 38 6
      reflex/app.py
  56. 3 1
      reflex/compiler/utils.py
  57. 1 1
      reflex/components/base/bare.py
  58. 7 6
      reflex/components/core/cond.py
  59. 1 1
      reflex/components/el/elements/forms.py
  60. 1 1
      reflex/components/el/elements/forms.pyi
  61. 1 1
      reflex/components/markdown/markdown.py
  62. 1 1
      reflex/components/radix/__init__.pyi
  63. 0 1
      reflex/components/radix/primitives/__init__.pyi
  64. 1 0
      reflex/components/radix/themes/components/__init__.pyi
  65. 1 1
      reflex/constants/base.py
  66. 1 0
      reflex/constants/event.py
  67. 25 4
      reflex/experimental/__init__.py
  68. 0 1
      reflex/ivars/__init__.py
  69. 269 189
      reflex/ivars/base.py
  70. 332 493
      reflex/ivars/number.py
  71. 75 237
      reflex/ivars/object.py
  72. 225 628
      reflex/ivars/sequence.py
  73. 22 0
      reflex/model.py
  74. 4 0
      reflex/reflex.py
  75. 48 31
      reflex/state.py
  76. 1 1
      reflex/utils/path_ops.py
  77. 34 14
      reflex/utils/prerequisites.py
  78. 2 2
      tests/components/core/test_colors.py
  79. 3 4
      tests/components/core/test_cond.py
  80. 1 10
      tests/test_app.py
  81. 106 0
      tests/test_health_endpoint.py
  82. 78 1
      tests/test_state.py
  83. 7 6
      tests/test_var.py

+ 1 - 0
.gitignore

@@ -3,6 +3,7 @@
 assets/external/*
 assets/external/*
 dist/*
 dist/*
 examples/
 examples/
+.web
 .idea
 .idea
 .vscode
 .vscode
 .coverage
 .coverage

+ 20 - 123
docker-example/README.md

@@ -1,133 +1,30 @@
-# Reflex Docker Container
+# Reflex Docker Examples
 
 
-This example describes how to create and use a container image for Reflex with your own code.
+This directory contains several examples of how to deploy Reflex apps using docker.
 
 
-## Update Requirements
+In all cases, ensure that your `requirements.txt` file is up to date and
+includes the `reflex` package.
 
 
-The `requirements.txt` includes the reflex package which is needed to install
-Reflex framework. If you use additional packages in your project you have to add
-this in the `requirements.txt` first. Copy the `Dockerfile`, `.dockerignore` and
-the `requirements.txt` file in your project folder.
+## `simple-two-port`
 
 
-## Build Simple Reflex Container Image
+The most basic production deployment exposes two HTTP ports and relies on an
+existing load balancer to forward the traffic appropriately.
 
 
-The main `Dockerfile` is intended to build a very simple, single container deployment that runs
-the Reflex frontend and backend together, exposing ports 3000 and 8000.
+## `simple-one-port`
 
 
-To build your container image run the following command:
+This deployment exports the frontend statically and serves it via a single HTTP
+port using Caddy. This is useful for platforms that only support a single port
+or where running a node server in the container is undesirable.
 
 
-```bash
-docker build -t reflex-app:latest .
-```
+## `production-compose`
 
 
-## Start Container Service
+This deployment is intended for use with a standalone VPS that is only hosting a
+single Reflex app. It provides the entire stack in a single `compose.yaml`
+including a webserver, one or more backend instances, redis, and a postgres
+database.
 
 
-Finally, you can start your Reflex container service as follows:
+## `production-app-platform`
 
 
-```bash
-docker run -it --rm -p 3000:3000 -p 8000:8000 --name app reflex-app:latest
-```
-
-It may take a few seconds for the service to become available.
-
-Access your app at http://localhost:3000.
-
-Note that this container has _no persistence_ and will lose all data when
-stopped. You can use bind mounts or named volumes to persist the database and
-uploaded_files directories as needed.
-
-# Production Service with Docker Compose and Caddy
-
-An example production deployment uses automatic TLS with Caddy serving static files
-for the frontend and proxying requests to both the frontend and backend.
-
-Copy the following files to your project directory:
-  * `compose.yaml`
-  * `compose.prod.yaml`
-  * `compose.tools.yaml`
-  * `prod.Dockerfile`
-  * `Caddy.Dockerfile`
-  * `Caddyfile`
-
-The production app container, based on `prod.Dockerfile`, builds and exports the
-frontend statically (to be served by Caddy). The resulting image only runs the
-backend service.
-
-The `webserver` service, based on `Caddy.Dockerfile`, copies the static frontend
-and `Caddyfile` into the container to configure the reverse proxy routes that will
-forward requests to the backend service. Caddy will automatically provision TLS
-for localhost or the domain specified in the environment variable `DOMAIN`.
-
-This type of deployment should use less memory and be more performant since
-nodejs is not required at runtime.
-
-## Customize `Caddyfile` (optional)
-
-If the app uses additional backend API routes, those should be added to the
-`@backend_routes` path matcher to ensure they are forwarded to the backend.
-
-## Build Reflex Production Service
-
-During build, set `DOMAIN` environment variable to the domain where the app will
-be hosted!  (Do not include http or https, it will always use https).
-
-**If `DOMAIN` is not provided, the service will default to `localhost`.**
-
-```bash
-DOMAIN=example.com docker compose build
-```
-
-This will build both the `app` service from the `prod.Dockerfile` and the `webserver`
-service via `Caddy.Dockerfile`.
-
-## Run Reflex Production Service
-
-```bash
-DOMAIN=example.com docker compose up
-```
-
-The app should be available at the specified domain via HTTPS. Certificate
-provisioning will occur automatically and may take a few minutes.
-
-### Data Persistence
-
-Named docker volumes are used to persist the app database (`db-data`),
-uploaded_files (`upload-data`), and caddy TLS keys and certificates
-(`caddy-data`).
-
-## More Robust Deployment
-
-For a more robust deployment, consider bringing the service up with
-`compose.prod.yaml` which includes postgres database and redis cache, allowing
-the backend to run with multiple workers and service more requests.
-
-```bash
-DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml up -d
-```
-
-Postgres uses its own named docker volume for data persistence.
-
-## Admin Tools
-
-When needed, the services in `compose.tools.yaml` can be brought up, providing
-graphical database administration (Adminer on http://localhost:8080) and a
-redis cache browser (redis-commander on http://localhost:8081). It is not recommended
-to deploy these services if they are not in active use.
-
-```bash
-DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml -f compose.tools.yaml up -d
-```
-
-# Container Hosting
-
-Most container hosting services automatically terminate TLS and expect the app
-to be listening on a single port (typically `$PORT`).
-
-To host a Reflex app on one of these platforms, like Google Cloud Run, Render,
-Railway, etc, use `app.Dockerfile` to build a single image containing a reverse
-proxy that will serve that frontend as static files and proxy requests to the
-backend for specific endpoints.
-
-If the chosen platform does not support buildx and thus heredoc, you can copy
-the Caddyfile configuration into a separate Caddyfile in the root of the
-project.
+This example deployment is intended for use with App hosting platforms, like
+Azure, AWS, or Google Cloud Run. It is the backend of the deployment, which
+depends on a separately hosted redis instance and static frontend deployment.

+ 5 - 0
docker-example/production-app-platform/.dockerignore

@@ -0,0 +1,5 @@
+.web
+.git
+__pycache__/*
+Dockerfile
+uploaded_files

+ 65 - 0
docker-example/production-app-platform/Dockerfile

@@ -0,0 +1,65 @@
+# This docker file is intended to be used with container hosting services
+#
+# After deploying this image, get the URL pointing to the backend service
+# and run API_URL=https://path-to-my-container.example.com reflex export frontend
+# then copy the contents of `frontend.zip` to your static file server (github pages, s3, etc).
+#
+# Azure Static Web App example:
+#    npx @azure/static-web-apps-cli deploy --env production --app-location .web/_static
+#
+# For dynamic routes to function properly, ensure that 404s are redirected to /404 on the
+# static file host (for github pages, this works out of the box; remember to create .nojekyll).
+#
+# For azure static web apps, add `staticwebapp.config.json` to to `.web/_static` with the following:
+#  {
+#     "responseOverrides": {
+#        "404": {
+#            "rewrite": "/404.html"
+#        }
+#     }
+#  }
+#
+# Note: many container hosting platforms require amd64 images, so when building on an M1 Mac
+# for example, pass `docker build --platform=linux/amd64 ...`
+
+# Stage 1: init
+FROM python:3.11 as init
+
+ARG uv=/root/.cargo/bin/uv
+
+# Install `uv` for faster package boostrapping
+ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
+RUN /install.sh && rm /install.sh
+
+# Copy local context to `/app` inside container (see .dockerignore)
+WORKDIR /app
+COPY . .
+RUN mkdir -p /app/data /app/uploaded_files
+
+# Create virtualenv which will be copied into final container
+ENV VIRTUAL_ENV=/app/.venv
+ENV PATH="$VIRTUAL_ENV/bin:$PATH"
+RUN $uv venv
+
+# Install app requirements and reflex inside virtualenv
+RUN $uv pip install -r requirements.txt
+
+# Deploy templates and prepare app
+RUN reflex init
+
+# Stage 2: copy artifacts into slim image 
+FROM python:3.11-slim
+WORKDIR /app
+RUN adduser --disabled-password --home /app reflex
+COPY --chown=reflex --from=init /app /app
+# Install libpq-dev for psycopg2 (skip if not using postgres).
+RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
+USER reflex
+ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1
+
+# Needed until Reflex properly passes SIGTERM on backend.
+STOPSIGNAL SIGKILL
+
+# Always apply migrations before starting the backend.
+CMD [ -d alembic ] && reflex db migrate; \
+    exec reflex run --env prod --backend-only --backend-port ${PORT:-8000}

+ 113 - 0
docker-example/production-app-platform/README.md

@@ -0,0 +1,113 @@
+# production-app-platform
+
+This example deployment is intended for use with App hosting platforms, like
+Azure, AWS, or Google Cloud Run.
+
+## Architecture
+
+The production deployment consists of a few pieces:
+  * Backend container - built by `Dockerfile` Runs the Reflex backend
+    service on port 8000 and is scalable to multiple instances.
+  * Redis container - A single instance the standard `redis` docker image should
+    share private networking with the backend
+  * Static frontend - HTML/CSS/JS files that are hosted via a CDN or static file
+    server. This is not included in the docker image.
+
+## Deployment
+
+These general steps do not cover the specifics of each platform, but all platforms should
+support the concepts described here.
+
+### Vnet
+
+All containers in the deployment should be hooked up to the same virtual private
+network so they can access the redis service and optionally the database server.
+The vnet should not be exposed to the internet, use an ingress rule to terminate
+TLS at the load balancer and forward the traffic to a backend service replica.
+
+### Redis
+
+Deploy a `redis` instance on the vnet.
+
+### Backend
+
+The backend is built by the `Dockerfile` in this directory. When deploying the
+backend, be sure to set REDIS_URL=redis://internal-redis-hostname to connect to
+the redis service.
+
+### Ingress
+
+Configure the load balancer for the app to forward traffic to port 8000 on the 
+backend service replicas. Most platforms will generate an ingress hostname
+automatically. Make sure when you access the ingress endpoint on `/ping` that it
+returns "pong", indicating that the backend is up an available.
+
+### Frontend
+
+The frontend should be hosted on a static file server or CDN.
+
+**Important**: when exporting the frontend, set the API_URL environment variable
+to the ingress hostname of the backend service.
+
+If you will host the frontend from a path other than the root, set the
+`FRONTEND_PATH` environment variable appropriately when exporting the frontend.
+
+Most static hosts will automatically use the `/404.html` file to handle 404
+errors. _This is essential for dynamic routes to work correctly._ Ensure that
+missing routes return the `/404.html` content to the user if this is not the
+default behavior.
+
+_For Github Pages_: ensure the file `.nojekyll` is present in the root of the repo
+to avoid special processing of underscore-prefix directories, like `_next`.
+
+## Platform Notes
+
+The following sections are currently a work in progress and may be incomplete.
+
+### Azure
+
+In the Azure load balancer, per-message deflate is not supported. Add the following
+to your `rxconfig.py` to workaround this issue.
+
+```python
+import uvicorn.workers
+
+import reflex as rx
+
+
+class NoWSPerMessageDeflate(uvicorn.workers.UvicornH11Worker):
+    CONFIG_KWARGS = {
+        **uvicorn.workers.UvicornH11Worker.CONFIG_KWARGS,
+        "ws_per_message_deflate": False,
+    }
+
+
+config = rx.Config(
+    app_name="my_app",
+    gunicorn_worker_class="rxconfig.NoWSPerMessageDeflate",
+)
+```
+
+#### Persistent Storage
+
+If you need to use a database or upload files, you cannot save them to the
+container volume. Use Azure Files and mount it into the container at /app/uploaded_files.
+
+#### Resource Types
+
+* Create a new vnet with 10.0.0.0/16
+  * Create a new subnet for redis, database, and containers
+* Deploy redis as a Container Instances
+* Deploy database server as "Azure Database for PostgreSQL"
+  * Create a new database for the app
+  * Set db-url as a secret containing the db user/password connection string
+* Deploy Storage account for uploaded files
+  * Enable access from the vnet and container subnet
+  * Create a new file share
+  * In the environment, create a new files share (get the storage key)
+* Deploy the backend as a Container App
+  * Create a custom Container App Environment linked up to the same vnet as the redis container.
+  * Set REDIS_URL and DB_URL environment variables
+  * Add the volume from the environment
+  * Add the volume mount to the container
+* Deploy the frontend as a Static Web App

+ 0 - 0
docker-example/.dockerignore → docker-example/production-compose/.dockerignore


+ 0 - 0
docker-example/Caddy.Dockerfile → docker-example/production-compose/Caddy.Dockerfile


+ 0 - 0
docker-example/Caddyfile → docker-example/production-compose/Caddyfile


+ 3 - 2
docker-example/prod.Dockerfile → docker-example/production-compose/Dockerfile

@@ -42,10 +42,11 @@ COPY --chown=reflex --from=init /app /app
 # Install libpq-dev for psycopg2 (skip if not using postgres).
 # Install libpq-dev for psycopg2 (skip if not using postgres).
 RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
 RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
 USER reflex
 USER reflex
-ENV PATH="/app/.venv/bin:$PATH"
+ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1
 
 
 # Needed until Reflex properly passes SIGTERM on backend.
 # Needed until Reflex properly passes SIGTERM on backend.
 STOPSIGNAL SIGKILL
 STOPSIGNAL SIGKILL
 
 
 # Always apply migrations before starting the backend.
 # Always apply migrations before starting the backend.
-CMD reflex db migrate && reflex run --env prod --backend-only
+CMD [ -d alembic ] && reflex db migrate; \
+    exec reflex run --env prod --backend-only

+ 75 - 0
docker-example/production-compose/README.md

@@ -0,0 +1,75 @@
+# production-compose
+
+This example production deployment uses automatic TLS with Caddy serving static
+files for the frontend and proxying requests to both the frontend and backend.
+It is intended for use with a standalone VPS that is only hosting a single
+Reflex app.
+
+The production app container (`Dockerfile`), builds and exports the frontend
+statically (to be served by Caddy). The resulting image only runs the backend
+service.
+
+The `webserver` service, based on `Caddy.Dockerfile`, copies the static frontend
+and `Caddyfile` into the container to configure the reverse proxy routes that will
+forward requests to the backend service. Caddy will automatically provision TLS
+for localhost or the domain specified in the environment variable `DOMAIN`.
+
+This type of deployment should use less memory and be more performant since
+nodejs is not required at runtime.
+
+## Customize `Caddyfile` (optional)
+
+If the app uses additional backend API routes, those should be added to the
+`@backend_routes` path matcher to ensure they are forwarded to the backend.
+
+## Build Reflex Production Service
+
+During build, set `DOMAIN` environment variable to the domain where the app will
+be hosted!  (Do not include http or https, it will always use https).
+
+**If `DOMAIN` is not provided, the service will default to `localhost`.**
+
+```bash
+DOMAIN=example.com docker compose build
+```
+
+This will build both the `app` service from the `prod.Dockerfile` and the `webserver`
+service via `Caddy.Dockerfile`.
+
+## Run Reflex Production Service
+
+```bash
+DOMAIN=example.com docker compose up
+```
+
+The app should be available at the specified domain via HTTPS. Certificate
+provisioning will occur automatically and may take a few minutes.
+
+### Data Persistence
+
+Named docker volumes are used to persist the app database (`db-data`),
+uploaded_files (`upload-data`), and caddy TLS keys and certificates
+(`caddy-data`).
+
+## More Robust Deployment
+
+For a more robust deployment, consider bringing the service up with
+`compose.prod.yaml` which includes postgres database and redis cache, allowing
+the backend to run with multiple workers and service more requests.
+
+```bash
+DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml up -d
+```
+
+Postgres uses its own named docker volume for data persistence.
+
+## Admin Tools
+
+When needed, the services in `compose.tools.yaml` can be brought up, providing
+graphical database administration (Adminer on http://localhost:8080) and a
+redis cache browser (redis-commander on http://localhost:8081). It is not recommended
+to deploy these services if they are not in active use.
+
+```bash
+DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml -f compose.tools.yaml up -d
+```

+ 0 - 0
docker-example/compose.prod.yaml → docker-example/production-compose/compose.prod.yaml


+ 0 - 0
docker-example/compose.tools.yaml → docker-example/production-compose/compose.tools.yaml


+ 0 - 1
docker-example/compose.yaml → docker-example/production-compose/compose.yaml

@@ -12,7 +12,6 @@ services:
       DB_URL: sqlite:///data/reflex.db
       DB_URL: sqlite:///data/reflex.db
     build:
     build:
       context: .
       context: .
-      dockerfile: prod.Dockerfile
     volumes:
     volumes:
        - db-data:/app/data
        - db-data:/app/data
        - upload-data:/app/uploaded_files
        - upload-data:/app/uploaded_files

+ 0 - 1
docker-example/requirements.txt

@@ -1 +0,0 @@
-reflex

+ 5 - 0
docker-example/simple-one-port/.dockerignore

@@ -0,0 +1,5 @@
+.web
+.git
+__pycache__/*
+Dockerfile
+uploaded_files

+ 14 - 0
docker-example/simple-one-port/Caddyfile

@@ -0,0 +1,14 @@
+:{$PORT}
+
+encode gzip
+
+@backend_routes path /_event/* /ping /_upload /_upload/*
+handle @backend_routes {
+	reverse_proxy localhost:8000
+}
+
+root * /srv
+route {
+	try_files {path} {path}/ /404.html
+	file_server
+}

+ 6 - 22
docker-example/app.Dockerfile → docker-example/simple-one-port/Dockerfile

@@ -10,31 +10,13 @@ FROM python:3.11
 ARG PORT=8080
 ARG PORT=8080
 # Only set for local/direct access. When TLS is used, the API_URL is assumed to be the same as the frontend.
 # Only set for local/direct access. When TLS is used, the API_URL is assumed to be the same as the frontend.
 ARG API_URL
 ARG API_URL
-ENV PORT=$PORT API_URL=${API_URL:-http://localhost:$PORT}
+ENV PORT=$PORT API_URL=${API_URL:-http://localhost:$PORT} REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
 
 
-# Install Caddy server inside image
-RUN apt-get update -y && apt-get install -y caddy && rm -rf /var/lib/apt/lists/*
+# Install Caddy and redis server inside image
+RUN apt-get update -y && apt-get install -y caddy redis-server && rm -rf /var/lib/apt/lists/*
 
 
 WORKDIR /app
 WORKDIR /app
 
 
-# Create a simple Caddyfile to serve as reverse proxy
-RUN cat > Caddyfile <<EOF
-:{\$PORT}
-
-encode gzip
-
-@backend_routes path /_event/* /ping /_upload /_upload/*
-handle @backend_routes {
-	reverse_proxy localhost:8000
-}
-
-root * /srv
-route {
-	try_files {path} {path}/ /404.html
-	file_server
-}
-EOF
-
 # Copy local context to `/app` inside container (see .dockerignore)
 # Copy local context to `/app` inside container (see .dockerignore)
 COPY . .
 COPY . .
 
 
@@ -54,4 +36,6 @@ EXPOSE $PORT
 
 
 # Apply migrations before starting the backend.
 # Apply migrations before starting the backend.
 CMD [ -d alembic ] && reflex db migrate; \
 CMD [ -d alembic ] && reflex db migrate; \
-    caddy start && reflex run --env prod --backend-only --loglevel debug 
+    caddy start && \
+    redis-server --daemonize yes && \
+    exec reflex run --env prod --backend-only

+ 36 - 0
docker-example/simple-one-port/README.md

@@ -0,0 +1,36 @@
+# simple-one-port
+
+This docker deployment runs Reflex in prod mode, exposing a single HTTP port:
+  * `8080` (`$PORT`) - Caddy server hosting the frontend statically and proxying requests to the backend.
+
+The deployment also runs a local Redis server to store state for each user.
+
+Using this method may be preferable for deploying in memory constrained
+environments, because it serves a static frontend export, rather than running
+the NextJS server via node.
+
+For platforms which only terminate TLS to a single port, this container can be
+deployed instead of the `simple-two-port` example.
+
+## Build
+
+```console
+docker build -t reflex-simple-one-port .
+```
+
+## Run
+
+```console
+docker run -p 8080:8080 reflex-simple-one-port
+```
+
+Note that this container has _no persistence_ and will lose all data when
+stopped. You can use bind mounts or named volumes to persist the database and
+uploaded_files directories as needed.
+
+## Usage
+
+This container should be used with an existing load balancer or reverse proxy to
+terminate TLS.
+
+It is also useful for deploying to simple app platforms, such as Render or Heroku.

+ 5 - 0
docker-example/simple-two-port/.dockerignore

@@ -0,0 +1,5 @@
+.web
+.git
+__pycache__/*
+Dockerfile
+uploaded_files

+ 7 - 2
docker-example/Dockerfile → docker-example/simple-two-port/Dockerfile

@@ -1,5 +1,8 @@
 # This Dockerfile is used to deploy a simple single-container Reflex app instance.
 # This Dockerfile is used to deploy a simple single-container Reflex app instance.
-FROM python:3.11
+FROM python:3.12
+
+RUN apt-get update && apt-get install -y redis-server && rm -rf /var/lib/apt/lists/*
+ENV REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
 
 
 # Copy local context to `/app` inside container (see .dockerignore)
 # Copy local context to `/app` inside container (see .dockerignore)
 WORKDIR /app
 WORKDIR /app
@@ -18,4 +21,6 @@ RUN reflex export --frontend-only --no-zip
 STOPSIGNAL SIGKILL
 STOPSIGNAL SIGKILL
 
 
 # Always apply migrations before starting the backend.
 # Always apply migrations before starting the backend.
-CMD [ -d alembic ] && reflex db migrate; reflex run --env prod
+CMD [ -d alembic ] && reflex db migrate; \
+    redis-server --daemonize yes && \
+    exec reflex run --env prod

+ 44 - 0
docker-example/simple-two-port/README.md

@@ -0,0 +1,44 @@
+# simple-two-port
+
+This docker deployment runs Reflex in prod mode, exposing two HTTP ports:
+  * `3000` - node NextJS server using optimized production build
+  * `8000` - python gunicorn server hosting the Reflex backend
+
+The deployment also runs a local Redis server to store state for each user.
+
+## Build
+
+```console
+docker build -t reflex-simple-two-port .
+```
+
+## Run
+
+```console
+docker run -p 3000:3000 -p 8000:8000 reflex-simple-two-port
+```
+
+Note that this container has _no persistence_ and will lose all data when
+stopped. You can use bind mounts or named volumes to persist the database and
+uploaded_files directories as needed.
+
+## Usage
+
+This container should be used with an existing load balancer or reverse proxy to
+route traffic to the appropriate port inside the container.
+
+For example, the following Caddyfile can be used to terminate TLS and forward
+traffic to the frontend and backend from outside the container.
+
+```
+my-domain.com
+
+encode gzip
+
+@backend_routes path /_event/* /ping /_upload /_upload/*
+handle @backend_routes {
+	reverse_proxy localhost:8000
+}
+
+reverse_proxy localhost:3000
+```

+ 39 - 0
integration/test_background_task.py

@@ -87,6 +87,13 @@ def BackgroundTask():
             third_state = await self.get_state(ThirdState)
             third_state = await self.get_state(ThirdState)
             await third_state._triple_count()
             await third_state._triple_count()
 
 
+        @rx.background
+        async def yield_in_async_with_self(self):
+            async with self:
+                self.counter += 1
+                yield
+                self.counter += 1
+
     class OtherState(rx.State):
     class OtherState(rx.State):
         @rx.background
         @rx.background
         async def get_other_state(self):
         async def get_other_state(self):
@@ -155,6 +162,11 @@ def BackgroundTask():
                 on_click=OtherState.get_other_state,
                 on_click=OtherState.get_other_state,
                 id="increment-from-other-state",
                 id="increment-from-other-state",
             ),
             ),
+            rx.button(
+                "Yield in Async with Self",
+                on_click=State.yield_in_async_with_self,
+                id="yield-in-async-with-self",
+            ),
             rx.button("Reset", on_click=State.reset_counter, id="reset"),
             rx.button("Reset", on_click=State.reset_counter, id="reset"),
         )
         )
 
 
@@ -334,3 +346,30 @@ def test_get_state(
 
 
     increment_button.click()
     increment_button.click()
     assert background_task._poll_for(lambda: counter.text == "13", timeout=5)
     assert background_task._poll_for(lambda: counter.text == "13", timeout=5)
+
+
+def test_yield_in_async_with_self(
+    background_task: AppHarness,
+    driver: WebDriver,
+    token: str,
+):
+    """Test that yielding inside async with self does not disable mutability.
+
+    Args:
+        background_task: harness for BackgroundTask app.
+        driver: WebDriver instance.
+        token: The token for the connected client.
+    """
+    assert background_task.app_instance is not None
+
+    # get a reference to all buttons
+    yield_in_async_with_self_button = driver.find_element(
+        By.ID, "yield-in-async-with-self"
+    )
+
+    # get a reference to the counter
+    counter = driver.find_element(By.ID, "counter")
+    assert background_task._poll_for(lambda: counter.text == "0", timeout=5)
+
+    yield_in_async_with_self_button.click()
+    assert background_task._poll_for(lambda: counter.text == "2", timeout=5)

+ 18 - 0
integration/test_var_operations.py

@@ -20,6 +20,9 @@ def VarOperations():
     from reflex.ivars.base import LiteralVar
     from reflex.ivars.base import LiteralVar
     from reflex.ivars.sequence import ArrayVar
     from reflex.ivars.sequence import ArrayVar
 
 
+    class Object(rx.Base):
+        str: str = "hello"
+
     class VarOperationState(rx.State):
     class VarOperationState(rx.State):
         int_var1: int = 10
         int_var1: int = 10
         int_var2: int = 5
         int_var2: int = 5
@@ -29,6 +32,7 @@ def VarOperations():
         list1: List = [1, 2]
         list1: List = [1, 2]
         list2: List = [3, 4]
         list2: List = [3, 4]
         list3: List = ["first", "second", "third"]
         list3: List = ["first", "second", "third"]
+        list4: List = [Object(name="obj_1"), Object(name="obj_2")]
         str_var1: str = "first"
         str_var1: str = "first"
         str_var2: str = "second"
         str_var2: str = "second"
         str_var3: str = "ThIrD"
         str_var3: str = "ThIrD"
@@ -474,6 +478,7 @@ def VarOperations():
             rx.text(
             rx.text(
                 VarOperationState.list1.contains(1).to_string(), id="list_contains"
                 VarOperationState.list1.contains(1).to_string(), id="list_contains"
             ),
             ),
+            rx.text(VarOperationState.list4.pluck("name").to_string(), id="list_pluck"),
             rx.text(VarOperationState.list1.reverse().to_string(), id="list_reverse"),
             rx.text(VarOperationState.list1.reverse().to_string(), id="list_reverse"),
             # LIST, INT
             # LIST, INT
             rx.text(
             rx.text(
@@ -588,6 +593,16 @@ def VarOperations():
                 int_var2=VarOperationState.int_var2,
                 int_var2=VarOperationState.int_var2,
                 id="memo_comp_nested",
                 id="memo_comp_nested",
             ),
             ),
+            # foreach in a match
+            rx.box(
+                rx.match(
+                    VarOperationState.list3.length(),
+                    (0, rx.text("No choices")),
+                    (1, rx.text("One choice")),
+                    rx.foreach(VarOperationState.list3, lambda choice: rx.text(choice)),
+                ),
+                id="foreach_in_match",
+            ),
         )
         )
 
 
 
 
@@ -749,6 +764,7 @@ def test_var_operations(driver, var_operations: AppHarness):
         ("list_and_list", "[3,4]"),
         ("list_and_list", "[3,4]"),
         ("list_or_list", "[1,2]"),
         ("list_or_list", "[1,2]"),
         ("list_contains", "true"),
         ("list_contains", "true"),
+        ("list_pluck", '["obj_1","obj_2"]'),
         ("list_reverse", "[2,1]"),
         ("list_reverse", "[2,1]"),
         ("list_join", "firstsecondthird"),
         ("list_join", "firstsecondthird"),
         ("list_join_comma", "first,second,third"),
         ("list_join_comma", "first,second,third"),
@@ -784,6 +800,8 @@ def test_var_operations(driver, var_operations: AppHarness):
         # rx.memo component with state
         # rx.memo component with state
         ("memo_comp", "1210"),
         ("memo_comp", "1210"),
         ("memo_comp_nested", "345"),
         ("memo_comp_nested", "345"),
+        # foreach in a match
+        ("foreach_in_match", "first\nsecond\nthird"),
     ]
     ]
 
 
     for tag, expected in tests:
     for tag, expected in tests:

+ 0 - 4
reflex/.templates/apps/demo/.gitignore

@@ -1,4 +0,0 @@
-*.db
-*.py[cod]
-.web
-__pycache__/

二进制
reflex/.templates/apps/demo/assets/favicon.ico


+ 0 - 10
reflex/.templates/apps/demo/assets/github.svg

@@ -1,10 +0,0 @@
-<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g id="Github" clip-path="url(#clip0_469_1929)">
-<path id="Vector" d="M8.0004 0.587524C3.80139 0.587524 0.400391 3.98851 0.400391 8.1875C0.400391 11.5505 2.57589 14.391 5.59689 15.398C5.97689 15.4645 6.11939 15.2365 6.11939 15.037C6.11939 14.8565 6.10989 14.258 6.10989 13.6215C4.20039 13.973 3.70639 13.156 3.55439 12.7285C3.46889 12.51 3.09839 11.8355 2.77539 11.655C2.50939 11.5125 2.12939 11.161 2.76589 11.1515C3.36439 11.142 3.79189 11.7025 3.93439 11.9305C4.61839 13.08 5.71089 12.757 6.14789 12.5575C6.21439 12.0635 6.41388 11.731 6.6324 11.541C4.94139 11.351 3.17439 10.6955 3.17439 7.7885C3.17439 6.962 3.46889 6.27801 3.95339 5.74601C3.87739 5.55601 3.61139 4.77701 4.02939 3.73201C4.02939 3.73201 4.66589 3.53251 6.11939 4.51101C6.7274 4.34001 7.3734 4.25451 8.0194 4.25451C8.6654 4.25451 9.3114 4.34001 9.9194 4.51101C11.3729 3.52301 12.0094 3.73201 12.0094 3.73201C12.4274 4.77701 12.1614 5.55601 12.0854 5.74601C12.5699 6.27801 12.8644 6.9525 12.8644 7.7885C12.8644 10.705 11.0879 11.351 9.3969 11.541C9.6724 11.7785 9.9099 12.2345 9.9099 12.947C9.9099 13.9635 9.9004 14.7805 9.9004 15.037C9.9004 15.2365 10.0429 15.474 10.4229 15.398C13.5165 14.3536 15.5996 11.4527 15.6004 8.1875C15.6004 3.98851 12.1994 0.587524 8.0004 0.587524Z" fill="#494369"/>
-</g>
-<defs>
-<clipPath id="clip0_469_1929">
-<rect width="16" height="16" fill="white"/>
-</clipPath>
-</defs>
-</svg>

+ 0 - 37
reflex/.templates/apps/demo/assets/icon.svg

@@ -1,37 +0,0 @@
-<svg width="67" height="14" viewBox="0 0 67 14" fill="none" xmlns="http://www.w3.org/2000/svg">
-<rect width="67" height="14" fill="#1E1E1E"/>
-<g id="Nav Template &#62; Initial" clip-path="url(#clip0_0_1)">
-<rect width="1440" height="1024" transform="translate(-16 -17)" fill="white"/>
-<g id="Sidebar">
-<g clip-path="url(#clip1_0_1)">
-<path d="M-16 -17H264V1007H-16V-17Z" fill="white"/>
-<g id="Header">
-<path d="M-16 -17H264V31H-16V-17Z" fill="white"/>
-<g id="Button">
-<rect x="-4" y="-3" width="74.316" height="20" rx="6" fill="white"/>
-<g id="Logo">
-<g id="Reflex">
-<path d="M0 13.6316V0.368408H10.6106V5.67369H7.95792V3.02105H2.65264V5.67369H7.95792V8.32633H2.65264V13.6316H0ZM7.95792 13.6316V8.32633H10.6106V13.6316H7.95792Z" fill="#110F1F"/>
-<path d="M13.2632 13.6316V0.368408H21.2211V3.02105H15.9158V5.67369H21.2211V8.32633H15.9158V10.979H21.2211V13.6316H13.2632Z" fill="#110F1F"/>
-<path d="M23.8738 13.6316V0.368408H31.8317V3.02105H26.5264V5.67369H31.8317V8.32633H26.5264V13.6316H23.8738Z" fill="#110F1F"/>
-<path d="M34.4843 13.6316V0.368408H37.137V10.979H42.4422V13.6316H34.4843Z" fill="#110F1F"/>
-<path d="M45.0949 13.6316V0.368408H53.0528V3.02105H47.7475V5.67369H53.0528V8.32633H47.7475V10.979H53.0528V13.6316H45.0949Z" fill="#110F1F"/>
-<path d="M55.7054 5.67369V0.368408H58.3581V5.67369H55.7054ZM63.6634 5.67369V0.368408H66.316V5.67369H63.6634ZM58.3581 8.32633V5.67369H63.6634V8.32633H58.3581ZM55.7054 13.6316V8.32633H58.3581V13.6316H55.7054ZM63.6634 13.6316V8.32633H66.316V13.6316H63.6634Z" fill="#110F1F"/>
-</g>
-</g>
-</g>
-<path d="M264 30.5H-16V31.5H264V30.5Z" fill="#F4F3F6"/>
-</g>
-</g>
-<path d="M263.5 -17V1007H264.5V-17H263.5Z" fill="#F4F3F6"/>
-</g>
-</g>
-<defs>
-<clipPath id="clip0_0_1">
-<rect width="1440" height="1024" fill="white" transform="translate(-16 -17)"/>
-</clipPath>
-<clipPath id="clip1_0_1">
-<path d="M-16 -17H264V1007H-16V-17Z" fill="white"/>
-</clipPath>
-</defs>
-</svg>

+ 0 - 68
reflex/.templates/apps/demo/assets/logo.svg

@@ -1,68 +0,0 @@
-<svg width="80" height="78" viewBox="0 0 80 78" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g filter="url(#filter0_ddddi_449_2821)">
-<path d="M13 11C13 6.58172 16.5817 3 21 3H59C63.4183 3 67 6.58172 67 11V49C67 52.3137 64.3137 55 61 55H19C15.6863 55 13 52.3137 13 49V11Z" fill="url(#paint0_radial_449_2821)"/>
-<path d="M13 11C13 6.58172 16.5817 3 21 3H59C63.4183 3 67 6.58172 67 11V49C67 52.3137 64.3137 55 61 55H19C15.6863 55 13 52.3137 13 49V11Z" fill="url(#paint1_radial_449_2821)"/>
-<g filter="url(#filter1_i_449_2821)">
-<path d="M31 37.5C30.4477 37.5 30 37.0523 30 36.5V13.5001C30 12.9478 30.4477 12.5001 31 12.5001H49C49.5523 12.5001 50 12.9478 50 13.5001V21.5001C50 22.0524 49.5523 22.5001 49 22.5001H45V18.5001C45 17.9478 44.5523 17.5001 44 17.5001H36C35.4477 17.5001 35 17.9478 35 18.5001V21.5001C35 22.0524 35.4477 22.5001 36 22.5001H45V27.5001H36C35.4477 27.5001 35 27.9478 35 28.5001V36.5C35 37.0523 34.5523 37.5 34 37.5H31ZM46 37.5C45.4477 37.5 45 37.0523 45 36.5V27.5001H49C49.5523 27.5001 50 27.9478 50 28.5001V36.5C50 37.0523 49.5523 37.5 49 37.5H46Z" fill="url(#paint2_radial_449_2821)"/>
-</g>
-<path d="M13 11C13 6.58172 16.5817 3 21 3H59C63.4183 3 67 6.58172 67 11V49C67 52.3137 64.3137 55 61 55H19C15.6863 55 13 52.3137 13 49V11Z" stroke="#20117E" stroke-opacity="0.04"/>
-</g>
-<defs>
-<filter id="filter0_ddddi_449_2821" x="0.5" y="0.5" width="79" height="77" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
-<feFlood flood-opacity="0" result="BackgroundImageFix"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feMorphology radius="4" operator="erode" in="SourceAlpha" result="effect1_dropShadow_449_2821"/>
-<feOffset dy="10"/>
-<feGaussianBlur stdDeviation="8"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.0784314 0 0 0 0 0.0705882 0 0 0 0 0.231373 0 0 0 0.06 0"/>
-<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_449_2821"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feMorphology radius="6" operator="erode" in="SourceAlpha" result="effect2_dropShadow_449_2821"/>
-<feOffset dy="12"/>
-<feGaussianBlur stdDeviation="3"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.0784314 0 0 0 0 0.0705882 0 0 0 0 0.231373 0 0 0 0.1 0"/>
-<feBlend mode="normal" in2="effect1_dropShadow_449_2821" result="effect2_dropShadow_449_2821"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feMorphology radius="4" operator="erode" in="SourceAlpha" result="effect3_dropShadow_449_2821"/>
-<feOffset dy="10"/>
-<feGaussianBlur stdDeviation="3"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.12549 0 0 0 0 0.0666667 0 0 0 0 0.494118 0 0 0 0.16 0"/>
-<feBlend mode="normal" in2="effect2_dropShadow_449_2821" result="effect3_dropShadow_449_2821"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feMorphology radius="1" operator="dilate" in="SourceAlpha" result="effect4_dropShadow_449_2821"/>
-<feOffset dy="2"/>
-<feGaussianBlur stdDeviation="1"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.12549 0 0 0 0 0.0666667 0 0 0 0 0.494118 0 0 0 0.05 0"/>
-<feBlend mode="normal" in2="effect3_dropShadow_449_2821" result="effect4_dropShadow_449_2821"/>
-<feBlend mode="normal" in="SourceGraphic" in2="effect4_dropShadow_449_2821" result="shape"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feOffset dy="-8"/>
-<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.678431 0 0 0 0 0.607843 0 0 0 0 0.972549 0 0 0 0.2 0"/>
-<feBlend mode="normal" in2="shape" result="effect5_innerShadow_449_2821"/>
-</filter>
-<filter id="filter1_i_449_2821" x="30" y="12.5001" width="20" height="26.9999" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
-<feFlood flood-opacity="0" result="BackgroundImageFix"/>
-<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feOffset dy="2"/>
-<feGaussianBlur stdDeviation="1.5"/>
-<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.12549 0 0 0 0 0.0666667 0 0 0 0 0.494118 0 0 0 0.32 0"/>
-<feBlend mode="normal" in2="shape" result="effect1_innerShadow_449_2821"/>
-</filter>
-<radialGradient id="paint0_radial_449_2821" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 3) rotate(90) scale(52 54)">
-<stop stop-color="white" stop-opacity="0.9"/>
-<stop offset="1" stop-color="#4E3DB9" stop-opacity="0.24"/>
-</radialGradient>
-<radialGradient id="paint1_radial_449_2821" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 3) rotate(90) scale(52 54)">
-<stop stop-color="white"/>
-<stop offset="1" stop-color="#F7F7F7"/>
-</radialGradient>
-<radialGradient id="paint2_radial_449_2821" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 12.5001) rotate(90) scale(24.9999 20)">
-<stop stop-color="#F5F3FF"/>
-<stop stop-color="white"/>
-<stop offset="1" stop-color="#E1DDF4"/>
-</radialGradient>
-</defs>
-</svg>

+ 0 - 13
reflex/.templates/apps/demo/assets/paneleft.svg

@@ -1,13 +0,0 @@
-<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g id="PaneLeft" clip-path="url(#clip0_469_1942)">
-<g id="Vector">
-<path fill-rule="evenodd" clip-rule="evenodd" d="M7.80217 0.525009C7.34654 0.525009 6.97717 0.894373 6.97717 1.35001V10.65C6.97717 11.1056 7.34654 11.475 7.80217 11.475H10.6522C11.1078 11.475 11.4772 11.1056 11.4772 10.65V1.35001C11.4772 0.894373 11.1078 0.525009 10.6522 0.525009H7.80217ZM8.02717 10.425V1.57501H10.4272V10.425H8.02717Z" fill="#494369"/>
-<path d="M3.78215 8.14502L2.16213 6.525H5.92717V5.475H2.16213L3.78215 3.85498L3.03969 3.11252L0.523438 5.62877V6.37123L3.03969 8.88748L3.78215 8.14502Z" fill="#494369"/>
-</g>
-</g>
-<defs>
-<clipPath id="clip0_469_1942">
-<rect width="12" height="12" fill="white"/>
-</clipPath>
-</defs>
-</svg>

+ 0 - 1
reflex/.templates/apps/demo/code/__init__.py

@@ -1 +0,0 @@
-"""Base template for Reflex."""

+ 0 - 127
reflex/.templates/apps/demo/code/demo.py

@@ -1,127 +0,0 @@
-"""Welcome to Reflex! This file outlines the steps to create a basic app."""
-
-from typing import Callable
-
-import reflex as rx
-
-from .pages import chatapp_page, datatable_page, forms_page, graphing_page, home_page
-from .sidebar import sidebar
-from .state import State
-from .styles import *
-
-meta = [
-    {
-        "name": "viewport",
-        "content": "width=device-width, shrink-to-fit=no, initial-scale=1",
-    },
-]
-
-
-def template(main_content: Callable[[], rx.Component]) -> rx.Component:
-    """The template for each page of the app.
-
-    Args:
-        main_content (Callable[[], rx.Component]): The main content of the page.
-
-    Returns:
-        rx.Component: The template for each page of the app.
-    """
-    menu_button = rx.chakra.box(
-        rx.chakra.menu(
-            rx.chakra.menu_button(
-                rx.chakra.icon(
-                    tag="hamburger",
-                    size="4em",
-                    color=text_color,
-                ),
-            ),
-            rx.chakra.menu_list(
-                rx.chakra.menu_item(rx.chakra.link("Home", href="/", width="100%")),
-                rx.chakra.menu_divider(),
-                rx.chakra.menu_item(
-                    rx.chakra.link(
-                        "About", href="https://github.com/reflex-dev", width="100%"
-                    )
-                ),
-                rx.chakra.menu_item(
-                    rx.chakra.link(
-                        "Contact", href="mailto:founders@reflex.dev", width="100%"
-                    )
-                ),
-            ),
-        ),
-        position="fixed",
-        right="1.5em",
-        top="1.5em",
-        z_index="500",
-    )
-
-    return rx.chakra.hstack(
-        sidebar(),
-        main_content(),
-        rx.chakra.spacer(),
-        menu_button,
-        align_items="flex-start",
-        transition="left 0.5s, width 0.5s",
-        position="relative",
-        left=rx.cond(State.sidebar_displayed, "0px", f"-{sidebar_width}"),
-    )
-
-
-@rx.page("/", meta=meta)
-@template
-def home() -> rx.Component:
-    """Home page.
-
-    Returns:
-        rx.Component: The home page.
-    """
-    return home_page()
-
-
-@rx.page("/forms", meta=meta)
-@template
-def forms() -> rx.Component:
-    """Forms page.
-
-    Returns:
-        rx.Component: The settings page.
-    """
-    return forms_page()
-
-
-@rx.page("/graphing", meta=meta)
-@template
-def graphing() -> rx.Component:
-    """Graphing page.
-
-    Returns:
-        rx.Component: The graphing page.
-    """
-    return graphing_page()
-
-
-@rx.page("/datatable", meta=meta)
-@template
-def datatable() -> rx.Component:
-    """Data Table page.
-
-    Returns:
-        rx.Component: The chatapp page.
-    """
-    return datatable_page()
-
-
-@rx.page("/chatapp", meta=meta)
-@template
-def chatapp() -> rx.Component:
-    """Chatapp page.
-
-    Returns:
-        rx.Component: The chatapp page.
-    """
-    return chatapp_page()
-
-
-# Create the app.
-app = rx.App(style=base_style)

+ 0 - 7
reflex/.templates/apps/demo/code/pages/__init__.py

@@ -1,7 +0,0 @@
-"""The pages of the app."""
-
-from .chatapp import chatapp_page
-from .datatable import datatable_page
-from .forms import forms_page
-from .graphing import graphing_page
-from .home import home_page

+ 0 - 31
reflex/.templates/apps/demo/code/pages/chatapp.py

@@ -1,31 +0,0 @@
-"""The main Chat app."""
-
-import reflex as rx
-
-from ..styles import *
-from ..webui import styles
-from ..webui.components import chat, modal, navbar, sidebar
-
-
-def chatapp_page() -> rx.Component:
-    """The main app.
-
-    Returns:
-        The UI for the main app.
-    """
-    return rx.chakra.box(
-        rx.chakra.vstack(
-            navbar(),
-            chat.chat(),
-            chat.action_bar(),
-            sidebar(),
-            modal(),
-            bg=styles.bg_dark_color,
-            color=styles.text_light_color,
-            min_h="100vh",
-            align_items="stretch",
-            spacing="0",
-            style=template_content_style,
-        ),
-        style=template_page_style,
-    )

+ 0 - 360
reflex/.templates/apps/demo/code/pages/datatable.py

@@ -1,360 +0,0 @@
-"""The settings page for the template."""
-
-from typing import Any
-
-import reflex as rx
-from reflex.components.datadisplay.dataeditor import DataEditorTheme
-
-from ..styles import *
-from ..webui.state import State
-
-
-class DataTableState(State):
-    """Datatable state."""
-
-    cols: list[Any] = [
-        {"title": "Title", "type": "str"},
-        {
-            "title": "Name",
-            "type": "str",
-            "group": "Data",
-            "width": 300,
-        },
-        {
-            "title": "Birth",
-            "type": "str",
-            "group": "Data",
-            "width": 150,
-        },
-        {
-            "title": "Human",
-            "type": "bool",
-            "group": "Data",
-            "width": 80,
-        },
-        {
-            "title": "House",
-            "type": "str",
-            "group": "Data",
-        },
-        {
-            "title": "Wand",
-            "type": "str",
-            "group": "Data",
-            "width": 250,
-        },
-        {
-            "title": "Patronus",
-            "type": "str",
-            "group": "Data",
-        },
-        {
-            "title": "Blood status",
-            "type": "str",
-            "group": "Data",
-            "width": 200,
-        },
-    ]
-
-    data = [
-        [
-            "1",
-            "Harry James Potter",
-            "31 July 1980",
-            True,
-            "Gryffindor",
-            "11'  Holly  phoenix feather",
-            "Stag",
-            "Half-blood",
-        ],
-        [
-            "2",
-            "Ronald Bilius Weasley",
-            "1 March 1980",
-            True,
-            "Gryffindor",
-            "12' Ash unicorn tail hair",
-            "Jack Russell terrier",
-            "Pure-blood",
-        ],
-        [
-            "3",
-            "Hermione Jean Granger",
-            "19 September, 1979",
-            True,
-            "Gryffindor",
-            "10¾'  vine wood dragon heartstring",
-            "Otter",
-            "Muggle-born",
-        ],
-        [
-            "4",
-            "Albus Percival Wulfric Brian Dumbledore",
-            "Late August 1881",
-            True,
-            "Gryffindor",
-            "15' Elder Thestral tail hair core",
-            "Phoenix",
-            "Half-blood",
-        ],
-        [
-            "5",
-            "Rubeus Hagrid",
-            "6 December 1928",
-            False,
-            "Gryffindor",
-            "16'  Oak unknown core",
-            "None",
-            "Part-Human (Half-giant)",
-        ],
-        [
-            "6",
-            "Fred Weasley",
-            "1 April, 1978",
-            True,
-            "Gryffindor",
-            "Unknown",
-            "Unknown",
-            "Pure-blood",
-        ],
-        [
-            "7",
-            "George Weasley",
-            "1 April, 1978",
-            True,
-            "Gryffindor",
-            "Unknown",
-            "Unknown",
-            "Pure-blood",
-        ],
-    ]
-
-
-code_show = """rx.chakra.hstack(
-    rx.chakra.divider(orientation="vertical", height="100vh", border="solid black 1px"),
-    rx.chakra.vstack(
-        rx.chakra.box(
-            rx.data_editor(
-                columns=DataTableState.cols,
-                data=DataTableState.data,
-                draw_focus_ring=True,
-                row_height=50,
-                smooth_scroll_x=True,
-                smooth_scroll_y=True,
-                column_select="single",
-                # style
-                theme=DataEditorTheme(**darkTheme),
-                width="80vw",
-                height="80vh",
-            ),
-        ),
-        rx.chakra.spacer(),
-        height="100vh",
-        spacing="25",
-    ),
-)"""
-
-state_show = """class DataTableState(State):
-    cols: list[Any] = [
-        {"title": "Title", "type": "str"},
-        {
-            "title": "Name",
-            "type": "str",
-            "group": "Data",
-            "width": 300,
-        },
-        {
-            "title": "Birth",
-            "type": "str",
-            "group": "Data",
-            "width": 150,
-        },
-        {
-            "title": "Human",
-            "type": "bool",
-            "group": "Data",
-            "width": 80,
-        },
-        {
-            "title": "House",
-            "type": "str",
-            "group": "Data",
-        },
-        {
-            "title": "Wand",
-            "type": "str",
-            "group": "Data",
-            "width": 250,
-        },
-        {
-            "title": "Patronus",
-            "type": "str",
-            "group": "Data",
-        },
-        {
-            "title": "Blood status",
-            "type": "str",
-            "group": "Data",
-            "width": 200,
-        },
-    ]"""
-
-data_show = """[
-    ["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11'  Holly  phoenix feather", "Stag", "Half-blood"],
-    ["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"],
-    ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾'  vine wood dragon heartstring", "Otter", "Muggle-born"],
-    ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"],
-    ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16'  Oak unknown core", "None", "Part-Human (Half-giant)"],
-    ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"],
-    ["7", "George Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"],
-]"""
-
-
-darkTheme = {
-    "accent_color": "#8c96ff",
-    "accent_light": "rgba(202, 206, 255, 0.253)",
-    "text_dark": "#ffffff",
-    "text_medium": "#b8b8b8",
-    "text_light": "#a0a0a0",
-    "text_bubble": "#ffffff",
-    "bg_icon_header": "#b8b8b8",
-    "fg_icon_header": "#000000",
-    "text_header": "#a1a1a1",
-    "text_header_selected": "#000000",
-    "bg_cell": "#16161b",
-    "bg_cell_medium": "#202027",
-    "bg_header": "#212121",
-    "bg_header_has_focus": "#474747",
-    "bg_header_hovered": "#404040",
-    "bg_bubble": "#212121",
-    "bg_bubble_selected": "#000000",
-    "bg_search_result": "#423c24",
-    "border_color": "rgba(225,225,225,0.2)",
-    "drilldown_border": "rgba(225,225,225,0.4)",
-    "link_color": "#4F5DFF",
-    "header_font_style": "bold 14px",
-    "base_font_style": "13px",
-    "font_family": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif",
-}
-
-darkTheme_show = """darkTheme={
-    "accent_color": "#8c96ff",
-    "accent_light": "rgba(202, 206, 255, 0.253)",
-    "text_dark": "#ffffff",
-    "text_medium": "#b8b8b8",
-    "text_light": "#a0a0a0",
-    "text_bubble": "#ffffff",
-    "bg_icon_header": "#b8b8b8",
-    "fg_icon_header": "#000000",
-    "text_header": "#a1a1a1",
-    "text_header_selected": "#000000",
-    "bg_cell": "#16161b",
-    "bg_cell_medium": "#202027",
-    "bg_header": "#212121",
-    "bg_header_has_focus": "#474747",
-    "bg_header_hovered": "#404040",
-    "bg_bubble": "#212121",
-    "bg_bubble_selected": "#000000",
-    "bg_search_result": "#423c24",
-    "border_color": "rgba(225,225,225,0.2)",
-    "drilldown_border": "rgba(225,225,225,0.4)",
-    "link_color": "#4F5DFF",
-    "header_font_style": "bold 14px",
-    "base_font_style": "13px",
-    "font_family": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif",
-}"""
-
-
-def datatable_page() -> rx.Component:
-    """The UI for the settings page.
-
-    Returns:
-        rx.Component: The UI for the settings page.
-    """
-    return rx.chakra.box(
-        rx.chakra.vstack(
-            rx.chakra.heading(
-                "Data Table Demo",
-                font_size="3em",
-            ),
-            rx.chakra.hstack(
-                rx.chakra.vstack(
-                    rx.chakra.box(
-                        rx.data_editor(
-                            columns=DataTableState.cols,
-                            data=DataTableState.data,
-                            draw_focus_ring=True,
-                            row_height=50,
-                            smooth_scroll_x=True,
-                            smooth_scroll_y=True,
-                            column_select="single",
-                            # style
-                            theme=DataEditorTheme(**darkTheme),
-                            width="80vw",
-                        ),
-                    ),
-                    rx.chakra.spacer(),
-                    spacing="25",
-                ),
-            ),
-            rx.chakra.tabs(
-                rx.chakra.tab_list(
-                    rx.chakra.tab("Code", style=tab_style),
-                    rx.chakra.tab("Data", style=tab_style),
-                    rx.chakra.tab("State", style=tab_style),
-                    rx.chakra.tab("Styling", style=tab_style),
-                    padding_x=0,
-                ),
-                rx.chakra.tab_panels(
-                    rx.chakra.tab_panel(
-                        rx.code_block(
-                            code_show,
-                            language="python",
-                            show_line_numbers=True,
-                        ),
-                        width="100%",
-                        padding_x=0,
-                        padding_y=".25em",
-                    ),
-                    rx.chakra.tab_panel(
-                        rx.code_block(
-                            data_show,
-                            language="python",
-                            show_line_numbers=True,
-                        ),
-                        width="100%",
-                        padding_x=0,
-                        padding_y=".25em",
-                    ),
-                    rx.chakra.tab_panel(
-                        rx.code_block(
-                            state_show,
-                            language="python",
-                            show_line_numbers=True,
-                        ),
-                        width="100%",
-                        padding_x=0,
-                        padding_y=".25em",
-                    ),
-                    rx.chakra.tab_panel(
-                        rx.code_block(
-                            darkTheme_show,
-                            language="python",
-                            show_line_numbers=True,
-                        ),
-                        width="100%",
-                        padding_x=0,
-                        padding_y=".25em",
-                    ),
-                    width="100%",
-                ),
-                variant="unstyled",
-                color_scheme="purple",
-                align="end",
-                width="100%",
-                padding_top=".5em",
-            ),
-            style=template_content_style,
-        ),
-        style=template_page_style,
-    )

+ 0 - 257
reflex/.templates/apps/demo/code/pages/forms.py

@@ -1,257 +0,0 @@
-"""The settings page for the template."""
-
-import reflex as rx
-
-from ..states.form_state import FormState, UploadState
-from ..styles import *
-
-forms_1_code = """rx.chakra.vstack(
-    rx.chakra.form(
-        rx.chakra.vstack(
-            rx.chakra.input(
-                placeholder="First Name",
-                id="first_name",
-            ),
-            rx.chakra.input(
-                placeholder="Last Name", id="last_name"
-            ),
-            rx.chakra.hstack(
-                rx.chakra.checkbox("Checked", id="check"),
-                rx.chakra.switch("Switched", id="switch"),
-            ),
-            rx.chakra.button("Submit",
-                       type_="submit",
-                       bg="#ecfdf5",
-                       color="#047857",
-                       border_radius="lg",
-            ),
-        ),
-        on_submit=FormState.handle_submit,
-    ),
-    rx.chakra.divider(),
-    rx.chakra.heading("Results"),
-    rx.chakra.text(FormState.form_data.to_string()),
-    width="100%",
-)"""
-
-color = "rgb(107,99,246)"
-
-forms_1_state = """class FormState(rx.State):
-
-    form_data: dict = {}
-
-    def handle_submit(self, form_data: dict):
-        "Handle the form submit."
-        self.form_data = form_data"""
-
-
-forms_2_code = """rx.chakra.vstack(
-    rx.upload(
-        rx.chakra.vstack(
-            rx.chakra.button(
-                "Select File",
-                color=color,
-                bg="white",
-                border=f"1px solid {color}",
-            ),
-            rx.chakra.text(
-                "Drag and drop files here or click to select files"
-            ),
-        ),
-        border=f"1px dotted {color}",
-        padding="5em",
-    ),
-    rx.chakra.hstack(rx.foreach(rx.selected_files, rx.chakra.text)),
-    rx.chakra.button(
-        "Upload",
-        on_click=lambda: UploadState.handle_upload(
-            rx.upload_files()
-        ),
-    ),
-    rx.chakra.button(
-        "Clear",
-        on_click=rx.clear_selected_files,
-    ),
-    rx.foreach(
-        UploadState.img, lambda img: rx.chakra.image(src=img, width="20%", height="auto",)
-    ),
-    padding="5em",
-    width="100%",
-)"""
-
-forms_2_state = """class UploadState(State):
-    "The app state."
-
-    # The images to show.
-    img: list[str]
-
-    async def handle_upload(
-        self, files: list[rx.UploadFile]
-    ):
-        "Handle the upload of file(s).
-
-        Args:
-            files: The uploaded files.
-        "
-        for file in files:
-            upload_data = await file.read()
-            outfile = rx.get_asset_path(file.filename)
-            # Save the file.
-            with open(outfile, "wb") as file_object:
-                file_object.write(upload_data)
-
-            # Update the img var.
-            self.img.append(f"/{file.filename}")"""
-
-
-def forms_page() -> rx.Component:
-    """The UI for the settings page.
-
-    Returns:
-        rx.Component: The UI for the settings page.
-    """
-    return rx.chakra.box(
-        rx.chakra.vstack(
-            rx.chakra.heading(
-                "Forms Demo",
-                font_size="3em",
-            ),
-            rx.chakra.vstack(
-                rx.chakra.form(
-                    rx.chakra.vstack(
-                        rx.chakra.input(
-                            placeholder="First Name",
-                            id="first_name",
-                        ),
-                        rx.chakra.input(placeholder="Last Name", id="last_name"),
-                        rx.chakra.hstack(
-                            rx.chakra.checkbox("Checked", id="check"),
-                            rx.chakra.switch("Switched", id="switch"),
-                        ),
-                        rx.chakra.button(
-                            "Submit",
-                            type_="submit",
-                            bg="#ecfdf5",
-                            color="#047857",
-                            border_radius="lg",
-                        ),
-                    ),
-                    on_submit=FormState.handle_submit,
-                ),
-                rx.chakra.divider(),
-                rx.chakra.heading("Results"),
-                rx.chakra.text(FormState.form_data.to_string()),
-                width="100%",
-            ),
-            rx.chakra.tabs(
-                rx.chakra.tab_list(
-                    rx.chakra.tab("Code", style=tab_style),
-                    rx.chakra.tab("State", style=tab_style),
-                    padding_x=0,
-                ),
-                rx.chakra.tab_panels(
-                    rx.chakra.tab_panel(
-                        rx.code_block(
-                            forms_1_code,
-                            language="python",
-                            show_line_numbers=True,
-                        ),
-                        width="100%",
-                        padding_x=0,
-                        padding_y=".25em",
-                    ),
-                    rx.chakra.tab_panel(
-                        rx.code_block(
-                            forms_1_state,
-                            language="python",
-                            show_line_numbers=True,
-                        ),
-                        width="100%",
-                        padding_x=0,
-                        padding_y=".25em",
-                    ),
-                    width="100%",
-                ),
-                variant="unstyled",
-                color_scheme="purple",
-                align="end",
-                width="100%",
-                padding_top=".5em",
-            ),
-            rx.chakra.heading("Upload Example", font_size="3em"),
-            rx.chakra.text("Try uploading some images and see how they look."),
-            rx.chakra.vstack(
-                rx.upload(
-                    rx.chakra.vstack(
-                        rx.chakra.button(
-                            "Select File",
-                            color=color,
-                            bg="white",
-                            border=f"1px solid {color}",
-                        ),
-                        rx.chakra.text(
-                            "Drag and drop files here or click to select files"
-                        ),
-                    ),
-                    border=f"1px dotted {color}",
-                    padding="5em",
-                ),
-                rx.chakra.hstack(rx.foreach(rx.selected_files, rx.chakra.text)),
-                rx.chakra.button(
-                    "Upload",
-                    on_click=lambda: UploadState.handle_upload(rx.upload_files()),
-                ),
-                rx.chakra.button(
-                    "Clear",
-                    on_click=rx.clear_selected_files,
-                ),
-                rx.foreach(
-                    UploadState.img,
-                    lambda img: rx.chakra.image(
-                        src=img,
-                        width="20%",
-                        height="auto",
-                    ),
-                ),
-                padding="5em",
-                width="100%",
-            ),
-            rx.chakra.tabs(
-                rx.chakra.tab_list(
-                    rx.chakra.tab("Code", style=tab_style),
-                    rx.chakra.tab("State", style=tab_style),
-                    padding_x=0,
-                ),
-                rx.chakra.tab_panels(
-                    rx.chakra.tab_panel(
-                        rx.code_block(
-                            forms_2_code,
-                            language="python",
-                            show_line_numbers=True,
-                        ),
-                        width="100%",
-                        padding_x=0,
-                        padding_y=".25em",
-                    ),
-                    rx.chakra.tab_panel(
-                        rx.code_block(
-                            forms_2_state,
-                            language="python",
-                            show_line_numbers=True,
-                        ),
-                        width="100%",
-                        padding_x=0,
-                        padding_y=".25em",
-                    ),
-                    width="100%",
-                ),
-                variant="unstyled",
-                color_scheme="purple",
-                align="end",
-                width="100%",
-                padding_top=".5em",
-            ),
-            style=template_content_style,
-        ),
-        style=template_page_style,
-    )

+ 0 - 253
reflex/.templates/apps/demo/code/pages/graphing.py

@@ -1,253 +0,0 @@
-"""The dashboard page for the template."""
-
-import reflex as rx
-
-from ..states.pie_state import PieChartState
-from ..styles import *
-
-data_1 = [
-    {"name": "Page A", "uv": 4000, "pv": 2400, "amt": 2400},
-    {"name": "Page B", "uv": 3000, "pv": 1398, "amt": 2210},
-    {"name": "Page C", "uv": 2000, "pv": 9800, "amt": 2290},
-    {"name": "Page D", "uv": 2780, "pv": 3908, "amt": 2000},
-    {"name": "Page E", "uv": 1890, "pv": 4800, "amt": 2181},
-    {"name": "Page F", "uv": 2390, "pv": 3800, "amt": 2500},
-    {"name": "Page G", "uv": 3490, "pv": 4300, "amt": 2100},
-]
-data_1_show = """[
-    {"name": "Page A", "uv": 4000, "pv": 2400, "amt": 2400},
-    {"name": "Page B", "uv": 3000, "pv": 1398, "amt": 2210},
-    {"name": "Page C", "uv": 2000, "pv": 9800, "amt": 2290},
-    {"name": "Page D", "uv": 2780, "pv": 3908, "amt": 2000},
-    {"name": "Page E", "uv": 1890, "pv": 4800, "amt": 2181},
-    {"name": "Page F", "uv": 2390, "pv": 3800, "amt": 2500},
-    {"name": "Page G", "uv": 3490, "pv": 4300, "amt": 2100},
-]"""
-
-
-graph_1_code = """rx.recharts.composed_chart(
-    rx.recharts.area(
-        data_key="uv", stroke="#8884d8", fill="#8884d8"
-    ),
-    rx.recharts.bar(
-        data_key="amt", bar_size=20, fill="#413ea0"
-    ),
-    rx.recharts.line(
-        data_key="pv", type_="monotone", stroke="#ff7300"
-    ),
-    rx.recharts.x_axis(data_key="name"),
-    rx.recharts.y_axis(),
-    rx.recharts.cartesian_grid(stroke_dasharray="3 3"),
-    rx.recharts.graphing_tooltip(),
-    data=data,
-)"""
-
-
-graph_2_code = """rx.recharts.pie_chart(
-    rx.recharts.pie(
-        data=PieChartState.resources,
-        data_key="count",
-        name_key="type_",
-        cx="50%",
-        cy="50%",
-        start_angle=180,
-        end_angle=0,
-        fill="#8884d8",
-        label=True,
-    ),
-    rx.recharts.graphing_tooltip(),
-),
-rx.chakra.vstack(
-    rx.foreach(
-        PieChartState.resource_types,
-        lambda type_, i: rx.chakra.hstack(
-            rx.chakra.button(
-                "-",
-                on_click=PieChartState.decrement(type_),
-            ),
-            rx.chakra.text(
-                type_,
-                PieChartState.resources[i]["count"],
-            ),
-            rx.chakra.button(
-                "+",
-                on_click=PieChartState.increment(type_),
-            ),
-        ),
-    ),
-)"""
-
-graph_2_state = """class PieChartState(rx.State):
-    resources: list[dict[str, Any]] = [
-        dict(type_="🏆", count=1),
-        dict(type_="🪵", count=1),
-        dict(type_="🥑", count=1),
-        dict(type_="🧱", count=1),
-    ]
-
-    @rx.cached_var
-    def resource_types(self) -> list[str]:
-        return [r["type_"] for r in self.resources]
-
-    def increment(self, type_: str):
-        for resource in self.resources:
-            if resource["type_"] == type_:
-                resource["count"] += 1
-                break
-
-    def decrement(self, type_: str):
-        for resource in self.resources:
-            if (
-                resource["type_"] == type_
-                and resource["count"] > 0
-            ):
-                resource["count"] -= 1
-                break
-"""
-
-
-def graphing_page() -> rx.Component:
-    """The UI for the dashboard page.
-
-    Returns:
-        rx.Component: The UI for the dashboard page.
-    """
-    return rx.chakra.box(
-        rx.chakra.vstack(
-            rx.chakra.heading(
-                "Graphing Demo",
-                font_size="3em",
-            ),
-            rx.chakra.heading(
-                "Composed Chart",
-                font_size="2em",
-            ),
-            rx.chakra.stack(
-                rx.recharts.composed_chart(
-                    rx.recharts.area(data_key="uv", stroke="#8884d8", fill="#8884d8"),
-                    rx.recharts.bar(data_key="amt", bar_size=20, fill="#413ea0"),
-                    rx.recharts.line(data_key="pv", type_="monotone", stroke="#ff7300"),
-                    rx.recharts.x_axis(data_key="name"),
-                    rx.recharts.y_axis(),
-                    rx.recharts.cartesian_grid(stroke_dasharray="3 3"),
-                    rx.recharts.graphing_tooltip(),
-                    data=data_1,
-                    # height="15em",
-                ),
-                width="100%",
-                height="20em",
-            ),
-            rx.chakra.tabs(
-                rx.chakra.tab_list(
-                    rx.chakra.tab("Code", style=tab_style),
-                    rx.chakra.tab("Data", style=tab_style),
-                    padding_x=0,
-                ),
-                rx.chakra.tab_panels(
-                    rx.chakra.tab_panel(
-                        rx.code_block(
-                            graph_1_code,
-                            language="python",
-                            show_line_numbers=True,
-                        ),
-                        width="100%",
-                        padding_x=0,
-                        padding_y=".25em",
-                    ),
-                    rx.chakra.tab_panel(
-                        rx.code_block(
-                            data_1_show,
-                            language="python",
-                            show_line_numbers=True,
-                        ),
-                        width="100%",
-                        padding_x=0,
-                        padding_y=".25em",
-                    ),
-                    width="100%",
-                ),
-                variant="unstyled",
-                color_scheme="purple",
-                align="end",
-                width="100%",
-                padding_top=".5em",
-            ),
-            rx.chakra.heading("Interactive Example", font_size="2em"),
-            rx.chakra.hstack(
-                rx.recharts.pie_chart(
-                    rx.recharts.pie(
-                        data=PieChartState.resources,
-                        data_key="count",
-                        name_key="type_",
-                        cx="50%",
-                        cy="50%",
-                        start_angle=180,
-                        end_angle=0,
-                        fill="#8884d8",
-                        label=True,
-                    ),
-                    rx.recharts.graphing_tooltip(),
-                ),
-                rx.chakra.vstack(
-                    rx.foreach(
-                        PieChartState.resource_types,
-                        lambda type_, i: rx.chakra.hstack(
-                            rx.chakra.button(
-                                "-",
-                                on_click=PieChartState.decrement(type_),
-                            ),
-                            rx.chakra.text(
-                                type_,
-                                PieChartState.resources[i]["count"],
-                            ),
-                            rx.chakra.button(
-                                "+",
-                                on_click=PieChartState.increment(type_),
-                            ),
-                        ),
-                    ),
-                ),
-                width="100%",
-                height="15em",
-            ),
-            rx.chakra.tabs(
-                rx.chakra.tab_list(
-                    rx.chakra.tab("Code", style=tab_style),
-                    rx.chakra.tab("State", style=tab_style),
-                    padding_x=0,
-                ),
-                rx.chakra.tab_panels(
-                    rx.chakra.tab_panel(
-                        rx.code_block(
-                            graph_2_code,
-                            language="python",
-                            show_line_numbers=True,
-                        ),
-                        width="100%",
-                        padding_x=0,
-                        padding_y=".25em",
-                    ),
-                    rx.chakra.tab_panel(
-                        rx.code_block(
-                            graph_2_state,
-                            language="python",
-                            show_line_numbers=True,
-                        ),
-                        width="100%",
-                        padding_x=0,
-                        padding_y=".25em",
-                    ),
-                    width="100%",
-                ),
-                variant="unstyled",
-                color_scheme="purple",
-                align="end",
-                width="100%",
-                padding_top=".5em",
-            ),
-            style=template_content_style,
-            min_h="100vh",
-        ),
-        style=template_page_style,
-        min_h="100vh",
-    )

+ 0 - 56
reflex/.templates/apps/demo/code/pages/home.py

@@ -1,56 +0,0 @@
-"""The home page of the app."""
-
-import reflex as rx
-
-from ..styles import *
-
-
-def home_page() -> rx.Component:
-    """The UI for the home page.
-
-    Returns:
-        rx.Component: The UI for the home page.
-    """
-    return rx.chakra.box(
-        rx.chakra.vstack(
-            rx.chakra.heading(
-                "Welcome to Reflex! 👋",
-                font_size="3em",
-            ),
-            rx.chakra.text(
-                "Reflex is an open-source app framework built specifically to allow you to build web apps in pure python. 👈 Select a demo from the sidebar to see some examples of what Reflex can do!",
-            ),
-            rx.chakra.heading(
-                "Things to check out:",
-                font_size="2em",
-            ),
-            rx.chakra.unordered_list(
-                rx.chakra.list_item(
-                    "Take a look at ",
-                    rx.chakra.link(
-                        "reflex.dev",
-                        href="https://reflex.dev",
-                        color="rgb(107,99,246)",
-                    ),
-                ),
-                rx.chakra.list_item(
-                    "Check out our ",
-                    rx.chakra.link(
-                        "docs",
-                        href="https://reflex.dev/docs/getting-started/introduction/",
-                        color="rgb(107,99,246)",
-                    ),
-                ),
-                rx.chakra.list_item(
-                    "Ask a question in our ",
-                    rx.chakra.link(
-                        "community",
-                        href="https://discord.gg/T5WSbC2YtQ",
-                        color="rgb(107,99,246)",
-                    ),
-                ),
-            ),
-            style=template_content_style,
-        ),
-        style=template_page_style,
-    )

+ 0 - 178
reflex/.templates/apps/demo/code/sidebar.py

@@ -1,178 +0,0 @@
-"""Sidebar component for the app."""
-
-import reflex as rx
-
-from .state import State
-from .styles import *
-
-
-def sidebar_header() -> rx.Component:
-    """Sidebar header.
-
-    Returns:
-        rx.Component: The sidebar header component.
-    """
-    return rx.chakra.hstack(
-        rx.chakra.image(
-            src="/icon.svg",
-            height="2em",
-        ),
-        rx.chakra.spacer(),
-        rx.chakra.link(
-            rx.chakra.center(
-                rx.chakra.image(
-                    src="/github.svg",
-                    height="3em",
-                    padding="0.5em",
-                ),
-                box_shadow=box_shadow,
-                bg="transparent",
-                border_radius=border_radius,
-                _hover={
-                    "bg": accent_color,
-                },
-            ),
-            href="https://github.com/reflex-dev/reflex",
-        ),
-        width="100%",
-        border_bottom=border,
-        padding="1em",
-    )
-
-
-def sidebar_footer() -> rx.Component:
-    """Sidebar footer.
-
-    Returns:
-        rx.Component: The sidebar footer component.
-    """
-    return rx.chakra.hstack(
-        rx.chakra.link(
-            rx.chakra.center(
-                rx.chakra.image(
-                    src="/paneleft.svg",
-                    height="2em",
-                    padding="0.5em",
-                ),
-                bg="transparent",
-                border_radius=border_radius,
-                **hover_accent_bg,
-            ),
-            on_click=State.toggle_sidebar_displayed,
-            transform=rx.cond(~State.sidebar_displayed, "rotate(180deg)", ""),
-            transition="transform 0.5s, left 0.5s",
-            position="relative",
-            left=rx.cond(State.sidebar_displayed, "0px", "20.5em"),
-            **overlapping_button_style,
-        ),
-        rx.chakra.spacer(),
-        rx.chakra.link(
-            rx.chakra.text(
-                "Docs",
-            ),
-            href="https://reflex.dev/docs/getting-started/introduction/",
-        ),
-        rx.chakra.link(
-            rx.chakra.text(
-                "Blog",
-            ),
-            href="https://reflex.dev/blog/",
-        ),
-        width="100%",
-        border_top=border,
-        padding="1em",
-    )
-
-
-def sidebar_item(text: str, icon: str, url: str) -> rx.Component:
-    """Sidebar item.
-
-    Args:
-        text (str): The text of the item.
-        icon (str): The icon of the item.
-        url (str): The URL of the item.
-
-    Returns:
-        rx.Component: The sidebar item component.
-    """
-    return rx.chakra.link(
-        rx.chakra.hstack(
-            rx.chakra.image(
-                src=icon,
-                height="2.5em",
-                padding="0.5em",
-            ),
-            rx.chakra.text(
-                text,
-            ),
-            bg=rx.cond(
-                State.origin_url == f"/{text.lower()}/",
-                accent_color,
-                "transparent",
-            ),
-            color=rx.cond(
-                State.origin_url == f"/{text.lower()}/",
-                accent_text_color,
-                text_color,
-            ),
-            border_radius=border_radius,
-            box_shadow=box_shadow,
-            width="100%",
-            padding_x="1em",
-        ),
-        href=url,
-        width="100%",
-    )
-
-
-def sidebar() -> rx.Component:
-    """Sidebar.
-
-    Returns:
-        rx.Component: The sidebar component.
-    """
-    return rx.chakra.box(
-        rx.chakra.vstack(
-            sidebar_header(),
-            rx.chakra.vstack(
-                sidebar_item(
-                    "Welcome",
-                    "/github.svg",
-                    "/",
-                ),
-                sidebar_item(
-                    "Graphing Demo",
-                    "/github.svg",
-                    "/graphing",
-                ),
-                sidebar_item(
-                    "Data Table Demo",
-                    "/github.svg",
-                    "/datatable",
-                ),
-                sidebar_item(
-                    "Forms Demo",
-                    "/github.svg",
-                    "/forms",
-                ),
-                sidebar_item(
-                    "Chat App Demo",
-                    "/github.svg",
-                    "/chatapp",
-                ),
-                width="100%",
-                overflow_y="auto",
-                align_items="flex-start",
-                padding="1em",
-            ),
-            rx.chakra.spacer(),
-            sidebar_footer(),
-            height="100dvh",
-        ),
-        display=["none", "none", "block"],
-        min_width=sidebar_width,
-        height="100%",
-        position="sticky",
-        top="0px",
-        border_right=border,
-    )

+ 0 - 22
reflex/.templates/apps/demo/code/state.py

@@ -1,22 +0,0 @@
-"""Base state for the app."""
-
-import reflex as rx
-
-
-class State(rx.State):
-    """State for the app."""
-
-    sidebar_displayed: bool = True
-
-    @rx.var
-    def origin_url(self) -> str:
-        """Get the url of the current page.
-
-        Returns:
-            str: The url of the current page.
-        """
-        return self.router_data.get("asPath", "")
-
-    def toggle_sidebar_displayed(self) -> None:
-        """Toggle the sidebar displayed."""
-        self.sidebar_displayed = not self.sidebar_displayed

+ 0 - 40
reflex/.templates/apps/demo/code/states/form_state.py

@@ -1,40 +0,0 @@
-import reflex as rx
-
-from ..state import State
-
-
-class FormState(State):
-    """Form state."""
-
-    form_data: dict = {}
-
-    def handle_submit(self, form_data: dict):
-        """Handle the form submit.
-
-        Args:
-            form_data: The form data.
-        """
-        self.form_data = form_data
-
-
-class UploadState(State):
-    """The app state."""
-
-    # The images to show.
-    img: list[str]
-
-    async def handle_upload(self, files: list[rx.UploadFile]):
-        """Handle the upload of file(s).
-
-        Args:
-            files: The uploaded files.
-        """
-        for file in files:
-            upload_data = await file.read()
-            outfile = rx.get_asset_path(file.filename)
-            # Save the file.
-            with open(outfile, "wb") as file_object:
-                file_object.write(upload_data)
-
-            # Update the img var.
-            self.img.append(f"/{file.filename}")

+ 0 - 47
reflex/.templates/apps/demo/code/states/pie_state.py

@@ -1,47 +0,0 @@
-from typing import Any
-
-import reflex as rx
-
-from ..state import State
-
-
-class PieChartState(State):
-    """Pie Chart State."""
-
-    resources: list[dict[str, Any]] = [
-        dict(type_="🏆", count=1),
-        dict(type_="🪵", count=1),
-        dict(type_="🥑", count=1),
-        dict(type_="🧱", count=1),
-    ]
-
-    @rx.cached_var
-    def resource_types(self) -> list[str]:
-        """Get the resource types.
-
-        Returns:
-            The resource types.
-        """
-        return [r["type_"] for r in self.resources]
-
-    def increment(self, type_: str):
-        """Increment the count of a resource type.
-
-        Args:
-            type_: The type of resource to increment.
-        """
-        for resource in self.resources:
-            if resource["type_"] == type_:
-                resource["count"] += 1
-                break
-
-    def decrement(self, type_: str):
-        """Decrement the count of a resource type.
-
-        Args:
-            type_: The type of resource to decrement.
-        """
-        for resource in self.resources:
-            if resource["type_"] == type_ and resource["count"] > 0:
-                resource["count"] -= 1
-                break

+ 0 - 68
reflex/.templates/apps/demo/code/styles.py

@@ -1,68 +0,0 @@
-"""Styles for the app."""
-
-import reflex as rx
-
-from .state import State
-
-border_radius = "0.375rem"
-box_shadow = "0px 0px 0px 1px rgba(84, 82, 95, 0.14)"
-border = "1px solid #F4F3F6"
-text_color = "black"
-accent_text_color = "#1A1060"
-accent_color = "#F5EFFE"
-hover_accent_color = {"_hover": {"color": accent_color}}
-hover_accent_bg = {"_hover": {"bg": accent_color}}
-content_width_vw = "90vw"
-sidebar_width = "20em"
-
-template_page_style = {
-    "padding_top": "5em",
-    "padding_x": "2em",
-}
-
-template_content_style = {
-    "width": rx.cond(
-        State.sidebar_displayed,
-        f"calc({content_width_vw} - {sidebar_width})",
-        content_width_vw,
-    ),
-    "min-width": sidebar_width,
-    "align_items": "flex-start",
-    "box_shadow": box_shadow,
-    "border_radius": border_radius,
-    "padding": "1em",
-    "margin_bottom": "2em",
-}
-
-link_style = {
-    "color": text_color,
-    "text_decoration": "none",
-    **hover_accent_color,
-}
-
-overlapping_button_style = {
-    "background_color": "white",
-    "border": border,
-    "border_radius": border_radius,
-}
-
-base_style = {
-    rx.chakra.MenuButton: {
-        "width": "3em",
-        "height": "3em",
-        **overlapping_button_style,
-    },
-    rx.chakra.MenuItem: hover_accent_bg,
-}
-
-tab_style = {
-    "color": "#494369",
-    "font_weight": 600,
-    "_selected": {
-        "color": "#5646ED",
-        "bg": "#F5EFFE",
-        "padding_x": "0.5em",
-        "padding_y": "0.25em",
-        "border_radius": "8px",
-    },
-}

+ 0 - 0
reflex/.templates/apps/demo/code/webui/__init__.py


+ 0 - 4
reflex/.templates/apps/demo/code/webui/components/__init__.py

@@ -1,4 +0,0 @@
-from .loading_icon import loading_icon
-from .modal import modal
-from .navbar import navbar
-from .sidebar import sidebar

+ 0 - 118
reflex/.templates/apps/demo/code/webui/components/chat.py

@@ -1,118 +0,0 @@
-import reflex as rx
-
-from ...webui import styles
-from ...webui.components import loading_icon
-from ...webui.state import QA, State
-
-
-def message(qa: QA) -> rx.Component:
-    """A single question/answer message.
-
-    Args:
-        qa: The question/answer pair.
-
-    Returns:
-        A component displaying the question/answer pair.
-    """
-    return rx.chakra.box(
-        rx.chakra.box(
-            rx.chakra.text(
-                qa.question,
-                bg=styles.border_color,
-                shadow=styles.shadow_light,
-                **styles.message_style,
-            ),
-            text_align="right",
-            margin_top="1em",
-        ),
-        rx.chakra.box(
-            rx.chakra.text(
-                qa.answer,
-                bg=styles.accent_color,
-                shadow=styles.shadow_light,
-                **styles.message_style,
-            ),
-            text_align="left",
-            padding_top="1em",
-        ),
-        width="100%",
-    )
-
-
-def chat() -> rx.Component:
-    """List all the messages in a single conversation.
-
-    Returns:
-        A component displaying all the messages in a single conversation.
-    """
-    return rx.chakra.vstack(
-        rx.chakra.box(rx.foreach(State.chats[State.current_chat], message)),
-        py="8",
-        flex="1",
-        width="100%",
-        max_w="3xl",
-        padding_x="4",
-        align_self="center",
-        overflow="hidden",
-        padding_bottom="5em",
-        **styles.base_style[rx.chakra.Vstack],
-    )
-
-
-def action_bar() -> rx.Component:
-    """The action bar to send a new message.
-
-    Returns:
-        The action bar to send a new message.
-    """
-    return rx.chakra.box(
-        rx.chakra.vstack(
-            rx.chakra.form(
-                rx.chakra.form_control(
-                    rx.chakra.hstack(
-                        rx.chakra.input(
-                            placeholder="Type something...",
-                            value=State.question,
-                            on_change=State.set_question,
-                            _placeholder={"color": "#fffa"},
-                            _hover={"border_color": styles.accent_color},
-                            style=styles.input_style,
-                        ),
-                        rx.chakra.button(
-                            rx.cond(
-                                State.processing,
-                                loading_icon(height="1em"),
-                                rx.chakra.text("Send"),
-                            ),
-                            type_="submit",
-                            _hover={"bg": styles.accent_color},
-                            style=styles.input_style,
-                        ),
-                        **styles.base_style[rx.chakra.Hstack],
-                    ),
-                    is_disabled=State.processing,
-                ),
-                on_submit=State.process_question,
-                width="100%",
-            ),
-            rx.chakra.text(
-                "ReflexGPT may return factually incorrect or misleading responses. Use discretion.",
-                font_size="xs",
-                color="#fff6",
-                text_align="center",
-            ),
-            width="100%",
-            max_w="3xl",
-            mx="auto",
-            **styles.base_style[rx.chakra.Vstack],
-        ),
-        position="sticky",
-        bottom="0",
-        left="0",
-        py="4",
-        backdrop_filter="auto",
-        backdrop_blur="lg",
-        border_top=f"1px solid {styles.border_color}",
-        align_items="stretch",
-        width="100%",
-    )

+ 0 - 19
reflex/.templates/apps/demo/code/webui/components/loading_icon.py

@@ -1,19 +0,0 @@
-import reflex as rx
-
-
-class LoadingIcon(rx.Component):
-    """A custom loading icon component."""
-
-    library = "react-loading-icons"
-    tag = "SpinningCircles"
-    stroke: rx.Var[str]
-    stroke_opacity: rx.Var[str]
-    fill: rx.Var[str]
-    fill_opacity: rx.Var[str]
-    stroke_width: rx.Var[str]
-    speed: rx.Var[str]
-    height: rx.Var[str]
-    on_change: rx.EventHandler[lambda status: [status]]
-
-
-loading_icon = LoadingIcon.create

+ 0 - 56
reflex/.templates/apps/demo/code/webui/components/modal.py

@@ -1,56 +0,0 @@
-import reflex as rx
-
-from ...webui.state import State
-
-
-def modal() -> rx.Component:
-    """A modal to create a new chat.
-
-    Returns:
-        The modal component.
-    """
-    return rx.chakra.modal(
-        rx.chakra.modal_overlay(
-            rx.chakra.modal_content(
-                rx.chakra.modal_header(
-                    rx.chakra.hstack(
-                        rx.chakra.text("Create new chat"),
-                        rx.chakra.icon(
-                            tag="close",
-                            font_size="sm",
-                            on_click=State.toggle_modal,
-                            color="#fff8",
-                            _hover={"color": "#fff"},
-                            cursor="pointer",
-                        ),
-                        align_items="center",
-                        justify_content="space-between",
-                    )
-                ),
-                rx.chakra.modal_body(
-                    rx.chakra.input(
-                        placeholder="Type something...",
-                        on_blur=State.set_new_chat_name,
-                        bg="#222",
-                        border_color="#fff3",
-                        _placeholder={"color": "#fffa"},
-                    ),
-                ),
-                rx.chakra.modal_footer(
-                    rx.chakra.button(
-                        "Create",
-                        bg="#5535d4",
-                        box_shadow="md",
-                        px="4",
-                        py="2",
-                        h="auto",
-                        _hover={"bg": "#4c2db3"},
-                        on_click=State.create_chat,
-                    ),
-                ),
-                bg="#222",
-                color="#fff",
-            ),
-        ),
-        is_open=State.modal_open,
-    )

+ 0 - 70
reflex/.templates/apps/demo/code/webui/components/navbar.py

@@ -1,70 +0,0 @@
-import reflex as rx
-
-from ...webui import styles
-from ...webui.state import State
-
-
-def navbar():
-    return rx.chakra.box(
-        rx.chakra.hstack(
-            rx.chakra.hstack(
-                rx.chakra.icon(
-                    tag="hamburger",
-                    mr=4,
-                    on_click=State.toggle_drawer,
-                    cursor="pointer",
-                ),
-                rx.chakra.link(
-                    rx.chakra.box(
-                        rx.chakra.image(src="favicon.ico", width=30, height="auto"),
-                        p="1",
-                        border_radius="6",
-                        bg="#F0F0F0",
-                        mr="2",
-                    ),
-                    href="/",
-                ),
-                rx.chakra.breadcrumb(
-                    rx.chakra.breadcrumb_item(
-                        rx.chakra.heading("ReflexGPT", size="sm"),
-                    ),
-                    rx.chakra.breadcrumb_item(
-                        rx.chakra.text(
-                            State.current_chat, size="sm", font_weight="normal"
-                        ),
-                    ),
-                ),
-            ),
-            rx.chakra.hstack(
-                rx.chakra.button(
-                    "+ New chat",
-                    bg=styles.accent_color,
-                    px="4",
-                    py="2",
-                    h="auto",
-                    on_click=State.toggle_modal,
-                ),
-                rx.chakra.menu(
-                    rx.chakra.menu_button(
-                        rx.chakra.avatar(name="User", size="md"),
-                        rx.chakra.box(),
-                    ),
-                    rx.chakra.menu_list(
-                        rx.chakra.menu_item("Help"),
-                        rx.chakra.menu_divider(),
-                        rx.chakra.menu_item("Settings"),
-                    ),
-                ),
-                spacing="8",
-            ),
-            justify="space-between",
-        ),
-        bg=styles.bg_dark_color,
-        backdrop_filter="auto",
-        backdrop_blur="lg",
-        p="4",
-        border_bottom=f"1px solid {styles.border_color}",
-        position="sticky",
-        top="0",
-        z_index="100",
-    )

+ 0 - 66
reflex/.templates/apps/demo/code/webui/components/sidebar.py

@@ -1,66 +0,0 @@
-import reflex as rx
-
-from ...webui import styles
-from ...webui.state import State
-
-
-def sidebar_chat(chat: str) -> rx.Component:
-    """A sidebar chat item.
-
-    Args:
-        chat: The chat item.
-
-    Returns:
-        The sidebar chat item.
-    """
-    return rx.chakra.hstack(
-        rx.chakra.box(
-            chat,
-            on_click=lambda: State.set_chat(chat),
-            style=styles.sidebar_style,
-            color=styles.icon_color,
-            flex="1",
-        ),
-        rx.chakra.box(
-            rx.chakra.icon(
-                tag="delete",
-                style=styles.icon_style,
-                on_click=State.delete_chat,
-            ),
-            style=styles.sidebar_style,
-        ),
-        color=styles.text_light_color,
-        cursor="pointer",
-    )
-
-
-def sidebar() -> rx.Component:
-    """The sidebar component.
-
-    Returns:
-        The sidebar component.
-    """
-    return rx.chakra.drawer(
-        rx.chakra.drawer_overlay(
-            rx.chakra.drawer_content(
-                rx.chakra.drawer_header(
-                    rx.chakra.hstack(
-                        rx.chakra.text("Chats"),
-                        rx.chakra.icon(
-                            tag="close",
-                            on_click=State.toggle_drawer,
-                            style=styles.icon_style,
-                        ),
-                    )
-                ),
-                rx.chakra.drawer_body(
-                    rx.chakra.vstack(
-                        rx.foreach(State.chat_titles, lambda chat: sidebar_chat(chat)),
-                        align_items="stretch",
-                    )
-                ),
-            ),
-        ),
-        placement="left",
-        is_open=State.drawer_open,
-    )

+ 0 - 146
reflex/.templates/apps/demo/code/webui/state.py

@@ -1,146 +0,0 @@
-import asyncio
-
-import reflex as rx
-
-from ..state import State
-
-# openai.api_key = os.environ["OPENAI_API_KEY"]
-# openai.api_base = os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1")
-
-
-class QA(rx.Base):
-    """A question and answer pair."""
-
-    question: str
-    answer: str
-
-
-DEFAULT_CHATS = {
-    "Intros": [],
-}
-
-
-class State(State):
-    """The app state."""
-
-    # A dict from the chat name to the list of questions and answers.
-    chats: dict[str, list[QA]] = DEFAULT_CHATS
-
-    # The current chat name.
-    current_chat = "Intros"
-
-    # The current question.
-    question: str
-
-    # Whether we are processing the question.
-    processing: bool = False
-
-    # The name of the new chat.
-    new_chat_name: str = ""
-
-    # Whether the drawer is open.
-    drawer_open: bool = False
-
-    # Whether the modal is open.
-    modal_open: bool = False
-
-    def create_chat(self):
-        """Create a new chat."""
-        # Add the new chat to the list of chats.
-        self.current_chat = self.new_chat_name
-        self.chats[self.new_chat_name] = []
-
-        # Toggle the modal.
-        self.modal_open = False
-
-    def toggle_modal(self):
-        """Toggle the new chat modal."""
-        self.modal_open = not self.modal_open
-
-    def toggle_drawer(self):
-        """Toggle the drawer."""
-        self.drawer_open = not self.drawer_open
-
-    def delete_chat(self):
-        """Delete the current chat."""
-        del self.chats[self.current_chat]
-        if len(self.chats) == 0:
-            self.chats = DEFAULT_CHATS
-        # set self.current_chat to the first chat.
-        self.current_chat = next(iter(self.chats))
-        self.toggle_drawer()
-
-    def set_chat(self, chat_name: str):
-        """Set the name of the current chat.
-
-        Args:
-            chat_name: The name of the chat.
-        """
-        self.current_chat = chat_name
-        self.toggle_drawer()
-
-    @rx.var
-    def chat_titles(self) -> list[str]:
-        """Get the list of chat titles.
-
-        Returns:
-            The list of chat names.
-        """
-        return [*self.chats]
-
-    async def process_question(self, form_data: dict[str, str]):
-        """Get the response from the API.
-
-        Args:
-            form_data: A dict with the current question.
-
-        Yields:
-            The current question and the response.
-        """
-        # Check if the question is empty
-        if self.question == "":
-            return
-
-        # Add the question to the list of questions.
-        qa = QA(question=self.question, answer="")
-        self.chats[self.current_chat].append(qa)
-
-        # Clear the input and start the processing.
-        self.processing = True
-        self.question = ""
-        yield
-
-        # # Build the messages.
-        # messages = [
-        #     {"role": "system", "content": "You are a friendly chatbot named Reflex."}
-        # ]
-        # for qa in self.chats[self.current_chat]:
-        #     messages.append({"role": "user", "content": qa.question})
-        #     messages.append({"role": "assistant", "content": qa.answer})
-
-        # # Remove the last mock answer.
-        # messages = messages[:-1]
-
-        # Start a new session to answer the question.
-        # session = openai.ChatCompletion.create(
-        #     model=os.getenv("OPENAI_MODEL", "gpt-3.5-turbo"),
-        #     messages=messages,
-        #     stream=True,
-        # )
-
-        # Stream the results, yielding after every word.
-        # for item in session:
-        answer = "I don't know! This Chatbot still needs to add in AI API keys!"
-        for i in range(len(answer)):
-            # Pause to show the streaming effect.
-            await asyncio.sleep(0.1)
-            # Add one letter at a time to the output.
-
-            # if hasattr(item.choices[0].delta, "content"):
-            #     answer_text = item.choices[0].delta.content
-            self.chats[self.current_chat][-1].answer += answer[i]
-            self.chats = self.chats
-            yield
-
-        # Toggle the processing flag.
-        self.processing = False

+ 0 - 88
reflex/.templates/apps/demo/code/webui/styles.py

@@ -1,88 +0,0 @@
-import reflex as rx
-
-bg_dark_color = "#111"
-bg_medium_color = "#222"
-
-border_color = "#fff3"
-
-accennt_light = "#6649D8"
-accent_color = "#5535d4"
-accent_dark = "#4c2db3"
-
-icon_color = "#fff8"
-
-text_light_color = "#fff"
-shadow_light = "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px;"
-shadow = "rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px, rgba(10, 37, 64, 0.35) 0px -2px 6px 0px inset;"
-
-message_style = dict(display="inline-block", p="4", border_radius="xl", max_w="30em")
-
-input_style = dict(
-    bg=bg_medium_color,
-    border_color=border_color,
-    border_width="1px",
-    p="4",
-)
-
-icon_style = dict(
-    font_size="md",
-    color=icon_color,
-    _hover=dict(color=text_light_color),
-    cursor="pointer",
-    w="8",
-)
-
-sidebar_style = dict(
-    border="double 1px transparent;",
-    border_radius="10px;",
-    background_image=f"linear-gradient({bg_dark_color}, {bg_dark_color}), radial-gradient(circle at top left, {accent_color},{accent_dark});",
-    background_origin="border-box;",
-    background_clip="padding-box, border-box;",
-    p="2",
-    _hover=dict(
-        background_image=f"linear-gradient({bg_dark_color}, {bg_dark_color}), radial-gradient(circle at top left, {accent_color},{accennt_light});",
-    ),
-)
-
-base_style = {
-    rx.chakra.Avatar: {
-        "shadow": shadow,
-        "color": text_light_color,
-        # "bg": border_color,
-    },
-    rx.chakra.Button: {
-        "shadow": shadow,
-        "color": text_light_color,
-        "_hover": {
-            "bg": accent_dark,
-        },
-    },
-    rx.chakra.Menu: {
-        "bg": bg_dark_color,
-        "border": f"red",
-    },
-    rx.chakra.MenuList: {
-        "bg": bg_dark_color,
-        "border": f"1.5px solid {bg_medium_color}",
-    },
-    rx.chakra.MenuDivider: {
-        "border": f"1px solid {bg_medium_color}",
-    },
-    rx.chakra.MenuItem: {
-        "bg": bg_dark_color,
-        "color": text_light_color,
-    },
-    rx.chakra.DrawerContent: {
-        "bg": bg_dark_color,
-        "color": text_light_color,
-        "opacity": "0.9",
-    },
-    rx.chakra.Hstack: {
-        "align_items": "center",
-        "justify_content": "space-between",
-    },
-    rx.chakra.Vstack: {
-        "align_items": "stretch",
-        "justify_content": "space-between",
-    },
-}

+ 2 - 2
reflex/.templates/jinja/web/pages/utils.js.jinja2

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

+ 1 - 1
reflex/__init__.py

@@ -140,6 +140,7 @@ RADIX_THEMES_COMPONENTS_MAPPING: dict = {
     "components.radix.themes.components.radio_group": ["radio", "radio_group"],
     "components.radix.themes.components.radio_group": ["radio", "radio_group"],
     "components.radix.themes.components.dropdown_menu": ["menu", "dropdown_menu"],
     "components.radix.themes.components.dropdown_menu": ["menu", "dropdown_menu"],
     "components.radix.themes.components.separator": ["divider", "separator"],
     "components.radix.themes.components.separator": ["divider", "separator"],
+    "components.radix.themes.components.progress": ["progress"],
 }
 }
 
 
 RADIX_THEMES_LAYOUT_MAPPING: dict = {
 RADIX_THEMES_LAYOUT_MAPPING: dict = {
@@ -205,7 +206,6 @@ RADIX_PRIMITIVES_MAPPING: dict = {
     "components.radix.primitives.form": [
     "components.radix.primitives.form": [
         "form",
         "form",
     ],
     ],
-    "components.radix.primitives.progress": ["progress"],
 }
 }
 
 
 COMPONENTS_CORE_MAPPING: dict = {
 COMPONENTS_CORE_MAPPING: dict = {

+ 1 - 1
reflex/__init__.pyi

@@ -71,7 +71,6 @@ from .components.plotly import plotly as plotly
 from .components.radix.primitives.accordion import accordion as accordion
 from .components.radix.primitives.accordion import accordion as accordion
 from .components.radix.primitives.drawer import drawer as drawer
 from .components.radix.primitives.drawer import drawer as drawer
 from .components.radix.primitives.form import form as form
 from .components.radix.primitives.form import form as form
-from .components.radix.primitives.progress import progress as progress
 from .components.radix.themes.base import theme as theme
 from .components.radix.themes.base import theme as theme
 from .components.radix.themes.base import theme_panel as theme_panel
 from .components.radix.themes.base import theme_panel as theme_panel
 from .components.radix.themes.color_mode import color_mode as color_mode
 from .components.radix.themes.color_mode import color_mode as color_mode
@@ -106,6 +105,7 @@ from .components.radix.themes.components.hover_card import hover_card as hover_c
 from .components.radix.themes.components.icon_button import icon_button as icon_button
 from .components.radix.themes.components.icon_button import icon_button as icon_button
 from .components.radix.themes.components.inset import inset as inset
 from .components.radix.themes.components.inset import inset as inset
 from .components.radix.themes.components.popover import popover as popover
 from .components.radix.themes.components.popover import popover as popover
+from .components.radix.themes.components.progress import progress as progress
 from .components.radix.themes.components.radio_cards import radio_cards as radio_cards
 from .components.radix.themes.components.radio_cards import radio_cards as radio_cards
 from .components.radix.themes.components.radio_group import radio as radio
 from .components.radix.themes.components.radio_group import radio as radio
 from .components.radix.themes.components.radio_group import radio_group as radio_group
 from .components.radix.themes.components.radio_group import radio_group as radio_group

+ 38 - 6
reflex/app.py

@@ -33,7 +33,7 @@ from typing import (
 
 
 from fastapi import FastAPI, HTTPException, Request, UploadFile
 from fastapi import FastAPI, HTTPException, Request, UploadFile
 from fastapi.middleware import cors
 from fastapi.middleware import cors
-from fastapi.responses import StreamingResponse
+from fastapi.responses import JSONResponse, StreamingResponse
 from fastapi.staticfiles import StaticFiles
 from fastapi.staticfiles import StaticFiles
 from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
 from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
 from socketio import ASGIApp, AsyncNamespace, AsyncServer
 from socketio import ASGIApp, AsyncNamespace, AsyncServer
@@ -65,7 +65,7 @@ from reflex.components.core.upload import Upload, get_upload_dir
 from reflex.components.radix import themes
 from reflex.components.radix import themes
 from reflex.config import get_config
 from reflex.config import get_config
 from reflex.event import Event, EventHandler, EventSpec, window_alert
 from reflex.event import Event, EventHandler, EventSpec, window_alert
-from reflex.model import Model
+from reflex.model import Model, get_db_status
 from reflex.page import (
 from reflex.page import (
     DECORATED_PAGES,
     DECORATED_PAGES,
 )
 )
@@ -269,13 +269,12 @@ class App(MiddlewareMixin, LifespanMixin, Base):
                 "`connect_error_component` is deprecated, use `overlay_component` instead"
                 "`connect_error_component` is deprecated, use `overlay_component` instead"
             )
             )
         super().__init__(**kwargs)
         super().__init__(**kwargs)
-        base_state_subclasses = BaseState.__subclasses__()
 
 
         # Special case to allow test cases have multiple subclasses of rx.BaseState.
         # Special case to allow test cases have multiple subclasses of rx.BaseState.
-        if not is_testing_env() and len(base_state_subclasses) > 1:
-            # Only one Base State class is allowed.
+        if not is_testing_env() and BaseState.__subclasses__() != [State]:
+            # Only rx.State is allowed as Base State subclass.
             raise ValueError(
             raise ValueError(
-                "rx.BaseState cannot be subclassed multiple times. use rx.State instead"
+                "rx.BaseState cannot be subclassed directly. Use rx.State instead"
             )
             )
 
 
         if "breakpoints" in self.style:
         if "breakpoints" in self.style:
@@ -377,6 +376,7 @@ class App(MiddlewareMixin, LifespanMixin, Base):
         """Add default api endpoints (ping)."""
         """Add default api endpoints (ping)."""
         # To test the server.
         # To test the server.
         self.api.get(str(constants.Endpoint.PING))(ping)
         self.api.get(str(constants.Endpoint.PING))(ping)
+        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)."""
@@ -1319,6 +1319,38 @@ async def ping() -> str:
     return "pong"
     return "pong"
 
 
 
 
+async def health() -> JSONResponse:
+    """Health check endpoint to assess the status of the database and Redis services.
+
+    Returns:
+        JSONResponse: A JSON object with the health status:
+            - "status" (bool): Overall health, True if all checks pass.
+            - "db" (bool or str): Database status - True, False, or "NA".
+            - "redis" (bool or str): Redis status - True, False, or "NA".
+    """
+    health_status = {"status": True}
+    status_code = 200
+
+    db_status, redis_status = await asyncio.gather(
+        get_db_status(), prerequisites.get_redis_status()
+    )
+
+    health_status["db"] = db_status
+
+    if redis_status is None:
+        health_status["redis"] = False
+    else:
+        health_status["redis"] = redis_status
+
+    if not health_status["db"] or (
+        not health_status["redis"] and redis_status is not None
+    ):
+        health_status["status"] = False
+        status_code = 503
+
+    return JSONResponse(content=health_status, status_code=status_code)
+
+
 def upload(app: App):
 def upload(app: App):
     """Upload a file.
     """Upload a file.
 
 

+ 3 - 1
reflex/compiler/utils.py

@@ -152,7 +152,9 @@ def compile_state(state: Type[BaseState]) -> dict:
         console.warn(
         console.warn(
             f"Failed to compile initial state with computed vars, excluding them: {e}"
             f"Failed to compile initial state with computed vars, excluding them: {e}"
         )
         )
-        initial_state = state(_reflex_internal_init=True).dict(include_computed=False)
+        initial_state = state(_reflex_internal_init=True).dict(
+            initial=True, include_computed=False
+        )
     return format.format_state(initial_state)
     return format.format_state(initial_state)
 
 
 
 

+ 1 - 1
reflex/components/base/bare.py

@@ -28,7 +28,7 @@ class Bare(Component):
         """
         """
         if isinstance(contents, ImmutableVar):
         if isinstance(contents, ImmutableVar):
             return cls(contents=contents)
             return cls(contents=contents)
-        if isinstance(contents, Var) and contents._get_all_var_data():
+        if isinstance(contents, Var):
             contents = contents.to(str)
             contents = contents.to(str)
         else:
         else:
             contents = str(contents) if contents is not None else ""
             contents = str(contents) if contents is not None else ""

+ 7 - 6
reflex/components/core/cond.py

@@ -9,7 +9,7 @@ from reflex.components.component import BaseComponent, Component, MemoizationLea
 from reflex.components.tags import CondTag, Tag
 from reflex.components.tags import CondTag, Tag
 from reflex.constants import Dirs
 from reflex.constants import Dirs
 from reflex.ivars.base import ImmutableVar, LiteralVar
 from reflex.ivars.base import ImmutableVar, LiteralVar
-from reflex.ivars.number import TernaryOperator
+from reflex.ivars.number import ternary_operation
 from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode
 from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode
 from reflex.utils.imports import ImportDict, ImportVar
 from reflex.utils.imports import ImportDict, ImportVar
 from reflex.vars import Var, VarData
 from reflex.vars import Var, VarData
@@ -163,11 +163,12 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | ImmutableVar:
     c2 = create_var(c2)
     c2 = create_var(c2)
 
 
     # Create the conditional var.
     # Create the conditional var.
-    return TernaryOperator.create(
-        condition=cond_var.to(bool),  # type: ignore
-        if_true=c1,
-        if_false=c2,
-        _var_data=VarData(imports=_IS_TRUE_IMPORT),
+    return ternary_operation(
+        cond_var.bool()._replace(  # type: ignore
+            merge_var_data=VarData(imports=_IS_TRUE_IMPORT),
+        ),  # type: ignore
+        c1,
+        c2,
     )
     )
 
 
 
 

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

@@ -501,7 +501,7 @@ AUTO_HEIGHT_JS = """
 const autoHeightOnInput = (e, is_enabled) => {
 const autoHeightOnInput = (e, is_enabled) => {
     if (is_enabled) {
     if (is_enabled) {
         const el = e.target;
         const el = e.target;
-        el.style.overflowY = "hidden";
+        el.style.overflowY = "scroll";
         el.style.height = "auto";
         el.style.height = "auto";
         el.style.height = (e.target.scrollHeight) + "px";
         el.style.height = (e.target.scrollHeight) + "px";
         if (el.form && !el.form.data_resize_on_reset) {
         if (el.form && !el.form.data_resize_on_reset) {

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

@@ -1559,7 +1559,7 @@ class Select(BaseHTML):
         """
         """
         ...
         ...
 
 
-AUTO_HEIGHT_JS = '\nconst autoHeightOnInput = (e, is_enabled) => {\n    if (is_enabled) {\n        const el = e.target;\n        el.style.overflowY = "hidden";\n        el.style.height = "auto";\n        el.style.height = (e.target.scrollHeight) + "px";\n        if (el.form && !el.form.data_resize_on_reset) {\n            el.form.addEventListener("reset", () => window.setTimeout(() => autoHeightOnInput(e, is_enabled), 0))\n            el.form.data_resize_on_reset = true;\n        }\n    }\n}\n'
+AUTO_HEIGHT_JS = '\nconst autoHeightOnInput = (e, is_enabled) => {\n    if (is_enabled) {\n        const el = e.target;\n        el.style.overflowY = "scroll";\n        el.style.height = "auto";\n        el.style.height = (e.target.scrollHeight) + "px";\n        if (el.form && !el.form.data_resize_on_reset) {\n            el.form.addEventListener("reset", () => window.setTimeout(() => autoHeightOnInput(e, is_enabled), 0))\n            el.form.data_resize_on_reset = true;\n        }\n    }\n}\n'
 ENTER_KEY_SUBMIT_JS = "\nconst enterKeySubmitOnKeyDown = (e, is_enabled) => {\n    if (is_enabled && e.which === 13 && !e.shiftKey) {\n        e.preventDefault();\n        if (!e.repeat) {\n            if (e.target.form) {\n                e.target.form.requestSubmit();\n            }\n        }\n    }\n}\n"
 ENTER_KEY_SUBMIT_JS = "\nconst enterKeySubmitOnKeyDown = (e, is_enabled) => {\n    if (is_enabled && e.which === 13 && !e.shiftKey) {\n        e.preventDefault();\n        if (!e.repeat) {\n            if (e.target.form) {\n                e.target.form.requestSubmit();\n            }\n        }\n    }\n}\n"
 
 
 class Textarea(BaseHTML):
 class Textarea(BaseHTML):

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

@@ -170,7 +170,7 @@ class Markdown(Component):
                 ),
                 ),
             },
             },
             *[
             *[
-                component(_MOCK_ARG)._get_imports()  # type: ignore
+                component(_MOCK_ARG)._get_all_imports()  # type: ignore
                 for component in self.component_map.values()
                 for component in self.component_map.values()
             ],
             ],
             CodeBlock.create(theme="light")._get_imports(),  # type: ignore,
             CodeBlock.create(theme="light")._get_imports(),  # type: ignore,

+ 1 - 1
reflex/components/radix/__init__.pyi

@@ -8,7 +8,6 @@ from . import themes as themes
 from .primitives.accordion import accordion as accordion
 from .primitives.accordion import accordion as accordion
 from .primitives.drawer import drawer as drawer
 from .primitives.drawer import drawer as drawer
 from .primitives.form import form as form
 from .primitives.form import form as form
-from .primitives.progress import progress as progress
 from .themes.base import theme as theme
 from .themes.base import theme as theme
 from .themes.base import theme_panel as theme_panel
 from .themes.base import theme_panel as theme_panel
 from .themes.color_mode import color_mode as color_mode
 from .themes.color_mode import color_mode as color_mode
@@ -31,6 +30,7 @@ from .themes.components.hover_card import hover_card as hover_card
 from .themes.components.icon_button import icon_button as icon_button
 from .themes.components.icon_button import icon_button as icon_button
 from .themes.components.inset import inset as inset
 from .themes.components.inset import inset as inset
 from .themes.components.popover import popover as popover
 from .themes.components.popover import popover as popover
+from .themes.components.progress import progress as progress
 from .themes.components.radio_cards import radio_cards as radio_cards
 from .themes.components.radio_cards import radio_cards as radio_cards
 from .themes.components.radio_group import radio as radio
 from .themes.components.radio_group import radio as radio
 from .themes.components.radio_group import radio_group as radio_group
 from .themes.components.radio_group import radio_group as radio_group

+ 0 - 1
reflex/components/radix/primitives/__init__.pyi

@@ -6,4 +6,3 @@
 from .accordion import accordion as accordion
 from .accordion import accordion as accordion
 from .drawer import drawer as drawer
 from .drawer import drawer as drawer
 from .form import form as form
 from .form import form as form
-from .progress import progress as progress

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

@@ -22,6 +22,7 @@ from .hover_card import hover_card as hover_card
 from .icon_button import icon_button as icon_button
 from .icon_button import icon_button as icon_button
 from .inset import inset as inset
 from .inset import inset as inset
 from .popover import popover as popover
 from .popover import popover as popover
+from .progress import progress as progress
 from .radio_cards import radio_cards as radio_cards
 from .radio_cards import radio_cards as radio_cards
 from .radio_group import radio as radio
 from .radio_group import radio as radio
 from .radio_group import radio_group as radio_group
 from .radio_group import radio_group as radio_group

+ 1 - 1
reflex/constants/base.py

@@ -105,7 +105,7 @@ class Templates(SimpleNamespace):
 
 
     # The reflex.build backend host
     # The reflex.build backend host
     REFLEX_BUILD_BACKEND = os.environ.get(
     REFLEX_BUILD_BACKEND = os.environ.get(
-        "REFLEX_BUILD_BACKEND", "https://rxh-prod-flexgen.fly.dev"
+        "REFLEX_BUILD_BACKEND", "https://flexgen-prod-flexgen.fly.dev"
     )
     )
 
 
     # The URL to redirect to reflex.build
     # The URL to redirect to reflex.build

+ 1 - 0
reflex/constants/event.py

@@ -11,6 +11,7 @@ class Endpoint(Enum):
     EVENT = "_event"
     EVENT = "_event"
     UPLOAD = "_upload"
     UPLOAD = "_upload"
     AUTH_CODESPACE = "auth-codespace"
     AUTH_CODESPACE = "auth-codespace"
+    HEALTH = "_health"
 
 
     def __str__(self) -> str:
     def __str__(self) -> str:
         """Get the string representation of the endpoint.
         """Get the string representation of the endpoint.

+ 25 - 4
reflex/experimental/__init__.py

@@ -32,18 +32,39 @@ class ExperimentalNamespace(SimpleNamespace):
         Returns:
         Returns:
             The toast namespace.
             The toast namespace.
         """
         """
-        if "toast" not in _EMITTED_PROMOTION_WARNINGS:
-            _EMITTED_PROMOTION_WARNINGS.add("toast")
-            warn(f"`rx._x.toast` was promoted to `rx.toast`.")
+        self.register_component_warning("toast")
         return toast
         return toast
 
 
+    @property
+    def progress(self):
+        """Temporary property returning the toast namespace.
+
+        Remove this property when toast is fully promoted.
+
+        Returns:
+            The toast namespace.
+        """
+        self.register_component_warning("progress")
+        return progress
+
+    @staticmethod
+    def register_component_warning(component_name: str):
+        """Add component to emitted warnings and throw a warning if it
+        doesn't exist.
+
+        Args:
+             component_name: name of the component.
+        """
+        if component_name not in _EMITTED_PROMOTION_WARNINGS:
+            _EMITTED_PROMOTION_WARNINGS.add(component_name)
+            warn(f"`rx._x.{component_name}` was promoted to `rx.{component_name}`.")
+
 
 
 _x = ExperimentalNamespace(
 _x = ExperimentalNamespace(
     asset=asset,
     asset=asset,
     client_state=ClientStateVar.create,
     client_state=ClientStateVar.create,
     hooks=hooks,
     hooks=hooks,
     layout=layout,
     layout=layout,
-    progress=progress,
     PropsBase=PropsBase,
     PropsBase=PropsBase,
     run_in_thread=run_in_thread,
     run_in_thread=run_in_thread,
 )
 )

+ 0 - 1
reflex/ivars/__init__.py

@@ -12,7 +12,6 @@ from .number import LiteralNumberVar as LiteralNumberVar
 from .number import NumberVar as NumberVar
 from .number import NumberVar as NumberVar
 from .object import LiteralObjectVar as LiteralObjectVar
 from .object import LiteralObjectVar as LiteralObjectVar
 from .object import ObjectVar as ObjectVar
 from .object import ObjectVar as ObjectVar
-from .sequence import ArrayJoinOperation as ArrayJoinOperation
 from .sequence import ArrayVar as ArrayVar
 from .sequence import ArrayVar as ArrayVar
 from .sequence import ConcatVarOperation as ConcatVarOperation
 from .sequence import ConcatVarOperation as ConcatVarOperation
 from .sequence import LiteralArrayVar as LiteralArrayVar
 from .sequence import LiteralArrayVar as LiteralArrayVar

+ 269 - 189
reflex/ivars/base.py

@@ -20,8 +20,8 @@ from typing import (
     Generic,
     Generic,
     List,
     List,
     Literal,
     Literal,
+    NoReturn,
     Optional,
     Optional,
-    Sequence,
     Set,
     Set,
     Tuple,
     Tuple,
     Type,
     Type,
@@ -384,10 +384,18 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
             return self.to(BooleanVar, output)
             return self.to(BooleanVar, output)
 
 
         if issubclass(output, NumberVar):
         if issubclass(output, NumberVar):
-            if fixed_type is not None and not issubclass(fixed_type, (int, float)):
-                raise TypeError(
-                    f"Unsupported type {var_type} for NumberVar. Must be int or float."
-                )
+            if fixed_type is not None:
+                if fixed_type is Union:
+                    inner_types = get_args(base_type)
+                    if not all(issubclass(t, (int, float)) for t in inner_types):
+                        raise TypeError(
+                            f"Unsupported type {var_type} for NumberVar. Must be int or float."
+                        )
+
+                elif not issubclass(fixed_type, (int, float)):
+                    raise TypeError(
+                        f"Unsupported type {var_type} for NumberVar. Must be int or float."
+                    )
             return ToNumberVarOperation.create(self, var_type or float)
             return ToNumberVarOperation.create(self, var_type or float)
 
 
         if issubclass(output, BooleanVar):
         if issubclass(output, BooleanVar):
@@ -440,7 +448,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Raises:
         Raises:
             TypeError: If the type is not supported for guessing.
             TypeError: If the type is not supported for guessing.
         """
         """
-        from .number import NumberVar
+        from .number import BooleanVar, NumberVar
         from .object import ObjectVar
         from .object import ObjectVar
         from .sequence import ArrayVar, StringVar
         from .sequence import ArrayVar, StringVar
 
 
@@ -454,11 +462,16 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         fixed_type = get_origin(var_type) or var_type
         fixed_type = get_origin(var_type) or var_type
 
 
         if fixed_type is Union:
         if fixed_type is Union:
+            inner_types = get_args(var_type)
+            if int in inner_types and float in inner_types:
+                return self.to(NumberVar, self._var_type)
             return self
             return self
 
 
         if not inspect.isclass(fixed_type):
         if not inspect.isclass(fixed_type):
             raise TypeError(f"Unsupported type {var_type} for guess_type.")
             raise TypeError(f"Unsupported type {var_type} for guess_type.")
 
 
+        if issubclass(fixed_type, bool):
+            return self.to(BooleanVar, self._var_type)
         if issubclass(fixed_type, (int, float)):
         if issubclass(fixed_type, (int, float)):
             return self.to(NumberVar, self._var_type)
             return self.to(NumberVar, self._var_type)
         if issubclass(fixed_type, dict):
         if issubclass(fixed_type, dict):
@@ -570,9 +583,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Returns:
         Returns:
             BooleanVar: A BooleanVar object representing the result of the equality check.
             BooleanVar: A BooleanVar object representing the result of the equality check.
         """
         """
-        from .number import EqualOperation
+        from .number import equal_operation
 
 
-        return EqualOperation.create(self, other)
+        return equal_operation(self, other)
 
 
     def __ne__(self, other: Var | Any) -> BooleanVar:
     def __ne__(self, other: Var | Any) -> BooleanVar:
         """Check if the current object is not equal to the given object.
         """Check if the current object is not equal to the given object.
@@ -583,9 +596,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Returns:
         Returns:
             BooleanVar: A BooleanVar object representing the result of the comparison.
             BooleanVar: A BooleanVar object representing the result of the comparison.
         """
         """
-        from .number import EqualOperation
+        from .number import equal_operation
 
 
-        return ~EqualOperation.create(self, other)
+        return ~equal_operation(self, other)
 
 
     def __gt__(self, other: Var | Any) -> BooleanVar:
     def __gt__(self, other: Var | Any) -> BooleanVar:
         """Compare the current instance with another variable and return a BooleanVar representing the result of the greater than operation.
         """Compare the current instance with another variable and return a BooleanVar representing the result of the greater than operation.
@@ -596,9 +609,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Returns:
         Returns:
             BooleanVar: A BooleanVar representing the result of the greater than operation.
             BooleanVar: A BooleanVar representing the result of the greater than operation.
         """
         """
-        from .number import GreaterThanOperation
+        from .number import greater_than_operation
 
 
-        return GreaterThanOperation.create(self, other)
+        return greater_than_operation(self, other)
 
 
     def __ge__(self, other: Var | Any) -> BooleanVar:
     def __ge__(self, other: Var | Any) -> BooleanVar:
         """Check if the value of this variable is greater than or equal to the value of another variable or object.
         """Check if the value of this variable is greater than or equal to the value of another variable or object.
@@ -609,9 +622,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Returns:
         Returns:
             BooleanVar: A BooleanVar object representing the result of the comparison.
             BooleanVar: A BooleanVar object representing the result of the comparison.
         """
         """
-        from .number import GreaterThanOrEqualOperation
+        from .number import greater_than_or_equal_operation
 
 
-        return GreaterThanOrEqualOperation.create(self, other)
+        return greater_than_or_equal_operation(self, other)
 
 
     def __lt__(self, other: Var | Any) -> BooleanVar:
     def __lt__(self, other: Var | Any) -> BooleanVar:
         """Compare the current instance with another variable using the less than (<) operator.
         """Compare the current instance with another variable using the less than (<) operator.
@@ -622,9 +635,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Returns:
         Returns:
             A `BooleanVar` object representing the result of the comparison.
             A `BooleanVar` object representing the result of the comparison.
         """
         """
-        from .number import LessThanOperation
+        from .number import less_than_operation
 
 
-        return LessThanOperation.create(self, other)
+        return less_than_operation(self, other)
 
 
     def __le__(self, other: Var | Any) -> BooleanVar:
     def __le__(self, other: Var | Any) -> BooleanVar:
         """Compare if the current instance is less than or equal to the given value.
         """Compare if the current instance is less than or equal to the given value.
@@ -635,9 +648,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Returns:
         Returns:
             A BooleanVar object representing the result of the comparison.
             A BooleanVar object representing the result of the comparison.
         """
         """
-        from .number import LessThanOrEqualOperation
+        from .number import less_than_or_equal_operation
 
 
-        return LessThanOrEqualOperation.create(self, other)
+        return less_than_or_equal_operation(self, other)
 
 
     def bool(self) -> BooleanVar:
     def bool(self) -> BooleanVar:
         """Convert the var to a boolean.
         """Convert the var to a boolean.
@@ -645,9 +658,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Returns:
         Returns:
             The boolean var.
             The boolean var.
         """
         """
-        from .number import ToBooleanVarOperation
+        from .number import boolify
 
 
-        return ToBooleanVarOperation.create(self)
+        return boolify(self)
 
 
     def __and__(self, other: Var | Any) -> ImmutableVar:
     def __and__(self, other: Var | Any) -> ImmutableVar:
         """Perform a logical AND operation on the current instance and another variable.
         """Perform a logical AND operation on the current instance and another variable.
@@ -658,7 +671,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Returns:
         Returns:
             A `BooleanVar` object representing the result of the logical AND operation.
             A `BooleanVar` object representing the result of the logical AND operation.
         """
         """
-        return AndOperation.create(self, other)
+        return and_operation(self, other)
 
 
     def __rand__(self, other: Var | Any) -> ImmutableVar:
     def __rand__(self, other: Var | Any) -> ImmutableVar:
         """Perform a logical AND operation on the current instance and another variable.
         """Perform a logical AND operation on the current instance and another variable.
@@ -669,7 +682,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Returns:
         Returns:
             A `BooleanVar` object representing the result of the logical AND operation.
             A `BooleanVar` object representing the result of the logical AND operation.
         """
         """
-        return AndOperation.create(other, self)
+        return and_operation(other, self)
 
 
     def __or__(self, other: Var | Any) -> ImmutableVar:
     def __or__(self, other: Var | Any) -> ImmutableVar:
         """Perform a logical OR operation on the current instance and another variable.
         """Perform a logical OR operation on the current instance and another variable.
@@ -680,7 +693,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Returns:
         Returns:
             A `BooleanVar` object representing the result of the logical OR operation.
             A `BooleanVar` object representing the result of the logical OR operation.
         """
         """
-        return OrOperation.create(self, other)
+        return or_operation(self, other)
 
 
     def __ror__(self, other: Var | Any) -> ImmutableVar:
     def __ror__(self, other: Var | Any) -> ImmutableVar:
         """Perform a logical OR operation on the current instance and another variable.
         """Perform a logical OR operation on the current instance and another variable.
@@ -691,7 +704,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Returns:
         Returns:
             A `BooleanVar` object representing the result of the logical OR operation.
             A `BooleanVar` object representing the result of the logical OR operation.
         """
         """
-        return OrOperation.create(other, self)
+        return or_operation(other, self)
 
 
     def __invert__(self) -> BooleanVar:
     def __invert__(self) -> BooleanVar:
         """Perform a logical NOT operation on the current instance.
         """Perform a logical NOT operation on the current instance.
@@ -699,9 +712,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
         Returns:
         Returns:
             A `BooleanVar` object representing the result of the logical NOT operation.
             A `BooleanVar` object representing the result of the logical NOT operation.
         """
         """
-        from .number import BooleanNotOperation
-
-        return BooleanNotOperation.create(self.bool())
+        return ~self.bool()
 
 
     def to_string(self) -> ImmutableVar:
     def to_string(self) -> ImmutableVar:
         """Convert the var to a string.
         """Convert the var to a string.
@@ -926,52 +937,92 @@ class LiteralVar(ImmutableVar):
 
 
 
 
 P = ParamSpec("P")
 P = ParamSpec("P")
-T = TypeVar("T", bound=ImmutableVar)
+T = TypeVar("T")
+
+
+# NoReturn is used to match CustomVarOperationReturn with no type hint.
+@overload
+def var_operation(
+    func: Callable[P, CustomVarOperationReturn[NoReturn]],
+) -> Callable[P, ImmutableVar]: ...
+
+
+@overload
+def var_operation(
+    func: Callable[P, CustomVarOperationReturn[bool]],
+) -> Callable[P, BooleanVar]: ...
+
+
+NUMBER_T = TypeVar("NUMBER_T", int, float, Union[int, float])
+
+
+@overload
+def var_operation(
+    func: Callable[P, CustomVarOperationReturn[NUMBER_T]],
+) -> Callable[P, NumberVar[NUMBER_T]]: ...
+
+
+@overload
+def var_operation(
+    func: Callable[P, CustomVarOperationReturn[str]],
+) -> Callable[P, StringVar]: ...
+
+
+LIST_T = TypeVar("LIST_T", bound=Union[List[Any], Tuple, Set])
 
 
 
 
-def var_operation(*, output: Type[T]) -> Callable[[Callable[P, str]], Callable[P, T]]:
+@overload
+def var_operation(
+    func: Callable[P, CustomVarOperationReturn[LIST_T]],
+) -> Callable[P, ArrayVar[LIST_T]]: ...
+
+
+OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Dict)
+
+
+@overload
+def var_operation(
+    func: Callable[P, CustomVarOperationReturn[OBJECT_TYPE]],
+) -> Callable[P, ObjectVar[OBJECT_TYPE]]: ...
+
+
+def var_operation(
+    func: Callable[P, CustomVarOperationReturn[T]],
+) -> Callable[P, ImmutableVar[T]]:
     """Decorator for creating a var operation.
     """Decorator for creating a var operation.
 
 
     Example:
     Example:
     ```python
     ```python
-    @var_operation(output=NumberVar)
+    @var_operation
     def add(a: NumberVar, b: NumberVar):
     def add(a: NumberVar, b: NumberVar):
-        return f"({a} + {b})"
+        return custom_var_operation(f"{a} + {b}")
     ```
     ```
 
 
     Args:
     Args:
-        output: The output type of the operation.
+        func: The function to decorate.
 
 
     Returns:
     Returns:
-        The decorator.
+        The decorated function.
     """
     """
 
 
-    def decorator(func: Callable[P, str], output=output):
-        @functools.wraps(func)
-        def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
-            args_vars = [
-                LiteralVar.create(arg) if not isinstance(arg, Var) else arg
-                for arg in args
-            ]
-            kwargs_vars = {
-                key: LiteralVar.create(value) if not isinstance(value, Var) else value
-                for key, value in kwargs.items()
-            }
-            return output(
-                _var_name=func(*args_vars, **kwargs_vars),  # type: ignore
-                _var_data=VarData.merge(
-                    *[arg._get_all_var_data() for arg in args if isinstance(arg, Var)],
-                    *[
-                        arg._get_all_var_data()
-                        for arg in kwargs.values()
-                        if isinstance(arg, Var)
-                    ],
-                ),
-            )
-
-        return wrapper
+    @functools.wraps(func)
+    def wrapper(*args: P.args, **kwargs: P.kwargs) -> ImmutableVar[T]:
+        func_args = list(inspect.signature(func).parameters)
+        args_vars = {
+            func_args[i]: (LiteralVar.create(arg) if not isinstance(arg, Var) else arg)
+            for i, arg in enumerate(args)
+        }
+        kwargs_vars = {
+            key: LiteralVar.create(value) if not isinstance(value, Var) else value
+            for key, value in kwargs.items()
+        }
+
+        return CustomVarOperation.create(
+            args=tuple(list(args_vars.items()) + list(kwargs_vars.items())),
+            return_var=func(*args_vars.values(), **kwargs_vars),  # type: ignore
+        ).guess_type()
 
 
-    return decorator
+    return wrapper
 
 
 
 
 def unionize(*args: Type) -> Type:
 def unionize(*args: Type) -> Type:
@@ -1100,114 +1151,64 @@ class CachedVarOperation:
         )
         )
 
 
 
 
-@dataclasses.dataclass(
-    eq=False,
-    frozen=True,
-    **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class AndOperation(CachedVarOperation, ImmutableVar):
-    """Class for the logical AND operation."""
-
-    # The first var.
-    _var1: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
-
-    # The second var.
-    _var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
-
-    @cached_property_no_lock
-    def _cached_var_name(self) -> str:
-        """Get the cached var name.
-
-        Returns:
-            The cached var name.
-        """
-        return f"({str(self._var1)} && {str(self._var2)})"
-
-    def __hash__(self) -> int:
-        """Calculates the hash value of the object.
+def and_operation(a: Var | Any, b: Var | Any) -> ImmutableVar:
+    """Perform a logical AND operation on two variables.
 
 
-        Returns:
-            int: The hash value of the object.
-        """
-        return hash((self.__class__.__name__, self._var1, self._var2))
+    Args:
+        a: The first variable.
+        b: The second variable.
 
 
-    @classmethod
-    def create(
-        cls, var1: Var | Any, var2: Var | Any, _var_data: VarData | None = None
-    ) -> AndOperation:
-        """Create an AndOperation.
+    Returns:
+        The result of the logical AND operation.
+    """
+    return _and_operation(a, b)  # type: ignore
 
 
-        Args:
-            var1: The first var.
-            var2: The second var.
-            _var_data: Additional hooks and imports associated with the Var.
 
 
-        Returns:
-            The AndOperation.
-        """
-        var1, var2 = map(LiteralVar.create, (var1, var2))
-        return AndOperation(
-            _var_name="",
-            _var_type=unionize(var1._var_type, var2._var_type),
-            _var_data=ImmutableVarData.merge(_var_data),
-            _var1=var1,
-            _var2=var2,
-        )
+@var_operation
+def _and_operation(a: ImmutableVar, b: ImmutableVar):
+    """Perform a logical AND operation on two variables.
 
 
+    Args:
+        a: The first variable.
+        b: The second variable.
 
 
-@dataclasses.dataclass(
-    eq=False,
-    frozen=True,
-    **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class OrOperation(CachedVarOperation, ImmutableVar):
-    """Class for the logical OR operation."""
+    Returns:
+        The result of the logical AND operation.
+    """
+    return var_operation_return(
+        js_expression=f"({a} && {b})",
+        var_type=unionize(a._var_type, b._var_type),
+    )
 
 
-    # The first var.
-    _var1: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
 
 
-    # The second var.
-    _var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
+def or_operation(a: Var | Any, b: Var | Any) -> ImmutableVar:
+    """Perform a logical OR operation on two variables.
 
 
-    @cached_property_no_lock
-    def _cached_var_name(self) -> str:
-        """Get the cached var name.
+    Args:
+        a: The first variable.
+        b: The second variable.
 
 
-        Returns:
-            The cached var name.
-        """
-        return f"({str(self._var1)} || {str(self._var2)})"
+    Returns:
+        The result of the logical OR operation.
+    """
+    return _or_operation(a, b)  # type: ignore
 
 
-    def __hash__(self) -> int:
-        """Calculates the hash value for the object.
 
 
-        Returns:
-            int: The hash value of the object.
-        """
-        return hash((self.__class__.__name__, self._var1, self._var2))
+@var_operation
+def _or_operation(a: ImmutableVar, b: ImmutableVar):
+    """Perform a logical OR operation on two variables.
 
 
-    @classmethod
-    def create(
-        cls, var1: Var | Any, var2: Var | Any, _var_data: VarData | None = None
-    ) -> OrOperation:
-        """Create an OrOperation.
-
-        Args:
-            var1: The first var.
-            var2: The second var.
-            _var_data: Additional hooks and imports associated with the Var.
+    Args:
+        a: The first variable.
+        b: The second variable.
 
 
-        Returns:
-            The OrOperation.
-        """
-        var1, var2 = map(LiteralVar.create, (var1, var2))
-        return OrOperation(
-            _var_name="",
-            _var_type=unionize(var1._var_type, var2._var_type),
-            _var_data=ImmutableVarData.merge(_var_data),
-            _var1=var1,
-            _var2=var2,
-        )
+    Returns:
+        The result of the logical OR operation.
+    """
+    return var_operation_return(
+        js_expression=f"({a} || {b})",
+        var_type=unionize(a._var_type, b._var_type),
+    )
 
 
 
 
 @dataclasses.dataclass(
 @dataclasses.dataclass(
@@ -1505,47 +1506,15 @@ class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
             The value of the var for the given instance.
             The value of the var for the given instance.
         """
         """
         if instance is None:
         if instance is None:
-            from reflex.state import BaseState
-
-            path_to_function = self.fget.__qualname__.split(".")
-            class_name_where_defined = (
-                path_to_function[-2] if len(path_to_function) > 1 else owner.__name__
-            )
-
-            def contains_class_name(states: Sequence[Type]) -> bool:
-                return any(c.__name__ == class_name_where_defined for c in states)
-
-            def is_not_mixin(state: Type[BaseState]) -> bool:
-                return not state._mixin
-
-            def length_of_state(state: Type[BaseState]) -> int:
-                return len(inspect.getmro(state))
-
-            class_where_defined = cast(
-                Type[BaseState],
-                min(
-                    filter(
-                        lambda state: state.__module__ == self.fget.__module__,
-                        filter(
-                            is_not_mixin,
-                            filter(
-                                lambda state: contains_class_name(
-                                    inspect.getmro(state)
-                                ),
-                                inspect.getmro(owner),
-                            ),
-                        ),
-                    ),
-                    default=owner,
-                    key=length_of_state,
-                ),
-            )
+            state_where_defined = owner
+            while self.fget.__name__ in state_where_defined.inherited_vars:
+                state_where_defined = state_where_defined.get_parent_state()
 
 
             return self._replace(
             return self._replace(
-                _var_name=format_state_name(class_where_defined.get_full_name())
+                _var_name=format_state_name(state_where_defined.get_full_name())
                 + "."
                 + "."
                 + self._var_name,
                 + self._var_name,
-                merge_var_data=ImmutableVarData.from_state(class_where_defined),
+                merge_var_data=ImmutableVarData.from_state(state_where_defined),
             ).guess_type()
             ).guess_type()
 
 
         if not self._cache:
         if not self._cache:
@@ -1797,3 +1766,114 @@ def immutable_computed_var(
         )
         )
 
 
     return wrapper
     return wrapper
+
+
+RETURN = TypeVar("RETURN")
+
+
+class CustomVarOperationReturn(ImmutableVar[RETURN]):
+    """Base class for custom var operations."""
+
+    @classmethod
+    def create(
+        cls,
+        js_expression: str,
+        _var_type: Type[RETURN] | None = None,
+        _var_data: VarData | None = None,
+    ) -> CustomVarOperationReturn[RETURN]:
+        """Create a CustomVarOperation.
+
+        Args:
+            js_expression: The JavaScript expression to evaluate.
+            _var_type: The type of the var.
+            _var_data: Additional hooks and imports associated with the Var.
+
+        Returns:
+            The CustomVarOperation.
+        """
+        return CustomVarOperationReturn(
+            _var_name=js_expression,
+            _var_type=_var_type or Any,
+            _var_data=ImmutableVarData.merge(_var_data),
+        )
+
+
+def var_operation_return(
+    js_expression: str,
+    var_type: Type[RETURN] | None = None,
+) -> CustomVarOperationReturn[RETURN]:
+    """Shortcut for creating a CustomVarOperationReturn.
+
+    Args:
+        js_expression: The JavaScript expression to evaluate.
+        var_type: The type of the var.
+
+    Returns:
+        The CustomVarOperationReturn.
+    """
+    return CustomVarOperationReturn.create(js_expression, var_type)
+
+
+@dataclasses.dataclass(
+    eq=False,
+    frozen=True,
+    **{"slots": True} if sys.version_info >= (3, 10) else {},
+)
+class CustomVarOperation(CachedVarOperation, ImmutableVar[T]):
+    """Base class for custom var operations."""
+
+    _args: Tuple[Tuple[str, Var], ...] = dataclasses.field(default_factory=tuple)
+
+    _return: CustomVarOperationReturn[T] = dataclasses.field(
+        default_factory=lambda: CustomVarOperationReturn.create("")
+    )
+
+    @cached_property_no_lock
+    def _cached_var_name(self) -> str:
+        """Get the cached var name.
+
+        Returns:
+            The cached var name.
+        """
+        return str(self._return)
+
+    @cached_property_no_lock
+    def _cached_get_all_var_data(self) -> ImmutableVarData | None:
+        """Get the cached VarData.
+
+        Returns:
+            The cached VarData.
+        """
+        return ImmutableVarData.merge(
+            *map(
+                lambda arg: arg[1]._get_all_var_data(),
+                self._args,
+            ),
+            self._return._get_all_var_data(),
+            self._var_data,
+        )
+
+    @classmethod
+    def create(
+        cls,
+        args: Tuple[Tuple[str, Var], ...],
+        return_var: CustomVarOperationReturn[T],
+        _var_data: VarData | None = None,
+    ) -> CustomVarOperation[T]:
+        """Create a CustomVarOperation.
+
+        Args:
+            args: The arguments to the operation.
+            return_var: The return var.
+            _var_data: Additional hooks and imports associated with the Var.
+
+        Returns:
+            The CustomVarOperation.
+        """
+        return CustomVarOperation(
+            _var_name="",
+            _var_type=return_var._var_type,
+            _var_data=ImmutableVarData.merge(_var_data),
+            _args=args,
+            _return=return_var,
+        )

文件差异内容过多而无法显示
+ 332 - 493
reflex/ivars/number.py


+ 75 - 237
reflex/ivars/object.py

@@ -30,6 +30,8 @@ from .base import (
     LiteralVar,
     LiteralVar,
     cached_property_no_lock,
     cached_property_no_lock,
     figure_out_type,
     figure_out_type,
+    var_operation,
+    var_operation_return,
 )
 )
 from .number import BooleanVar, NumberVar
 from .number import BooleanVar, NumberVar
 from .sequence import ArrayVar, StringVar
 from .sequence import ArrayVar, StringVar
@@ -56,7 +58,9 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
         return str
         return str
 
 
     @overload
     @overload
-    def _value_type(self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]) -> VALUE_TYPE: ...
+    def _value_type(
+        self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]],
+    ) -> Type[VALUE_TYPE]: ...
 
 
     @overload
     @overload
     def _value_type(self) -> Type: ...
     def _value_type(self) -> Type: ...
@@ -79,7 +83,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
         Returns:
         Returns:
             The keys of the object.
             The keys of the object.
         """
         """
-        return ObjectKeysOperation.create(self)
+        return object_keys_operation(self)
 
 
     @overload
     @overload
     def values(
     def values(
@@ -95,7 +99,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
         Returns:
         Returns:
             The values of the object.
             The values of the object.
         """
         """
-        return ObjectValuesOperation.create(self)
+        return object_values_operation(self)
 
 
     @overload
     @overload
     def entries(
     def entries(
@@ -111,9 +115,9 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
         Returns:
         Returns:
             The entries of the object.
             The entries of the object.
         """
         """
-        return ObjectEntriesOperation.create(self)
+        return object_entries_operation(self)
 
 
-    def merge(self, other: ObjectVar) -> ObjectMergeOperation:
+    def merge(self, other: ObjectVar):
         """Merge two objects.
         """Merge two objects.
 
 
         Args:
         Args:
@@ -122,7 +126,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
         Returns:
         Returns:
             The merged object.
             The merged object.
         """
         """
-        return ObjectMergeOperation.create(self, other)
+        return object_merge_operation(self, other)
 
 
     # NoReturn is used here to catch when key value is Any
     # NoReturn is used here to catch when key value is Any
     @overload
     @overload
@@ -270,7 +274,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
         Returns:
         Returns:
             The result of the check.
             The result of the check.
         """
         """
-        return ObjectHasOwnProperty.create(self, key)
+        return object_has_own_property_operation(self, key)
 
 
 
 
 @dataclasses.dataclass(
 @dataclasses.dataclass(
@@ -387,207 +391,72 @@ class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar):
         )
         )
 
 
 
 
-@dataclasses.dataclass(
-    eq=False,
-    frozen=True,
-    **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class ObjectToArrayOperation(CachedVarOperation, ArrayVar):
-    """Base class for object to array operations."""
-
-    _value: ObjectVar = dataclasses.field(
-        default_factory=lambda: LiteralObjectVar.create({})
-    )
-
-    @cached_property_no_lock
-    def _cached_var_name(self) -> str:
-        """The name of the operation.
-
-        Raises:
-            NotImplementedError: Must implement _cached_var_name.
-        """
-        raise NotImplementedError(
-            "ObjectToArrayOperation must implement _cached_var_name"
-        )
-
-    @classmethod
-    def create(
-        cls,
-        value: ObjectVar,
-        _var_type: GenericType | None = None,
-        _var_data: VarData | None = None,
-    ) -> ObjectToArrayOperation:
-        """Create the object to array operation.
-
-        Args:
-            value: The value of the operation.
-            _var_data: Additional hooks and imports associated with the operation.
-
-        Returns:
-            The object to array operation.
-        """
-        return cls(
-            _var_name="",
-            _var_type=list if _var_type is None else _var_type,
-            _var_data=ImmutableVarData.merge(_var_data),
-            _value=value,
-        )
-
-
-class ObjectKeysOperation(ObjectToArrayOperation):
-    """Operation to get the keys of an object."""
-
-    @cached_property_no_lock
-    def _cached_var_name(self) -> str:
-        """The name of the operation.
-
-        Returns:
-            The name of the operation.
-        """
-        return f"Object.keys({str(self._value)})"
-
-    @classmethod
-    def create(
-        cls,
-        value: ObjectVar,
-        _var_data: VarData | None = None,
-    ) -> ObjectKeysOperation:
-        """Create the object keys operation.
-
-        Args:
-            value: The value of the operation.
-            _var_data: Additional hooks and imports associated with the operation.
-
-        Returns:
-            The object keys operation.
-        """
-        return cls(
-            _var_name="",
-            _var_type=List[str],
-            _var_data=ImmutableVarData.merge(_var_data),
-            _value=value,
-        )
-
-
-class ObjectValuesOperation(ObjectToArrayOperation):
-    """Operation to get the values of an object."""
-
-    @cached_property_no_lock
-    def _cached_var_name(self) -> str:
-        """The name of the operation.
-
-        Returns:
-            The name of the operation.
-        """
-        return f"Object.values({str(self._value)})"
-
-    @classmethod
-    def create(
-        cls,
-        value: ObjectVar,
-        _var_data: VarData | None = None,
-    ) -> ObjectValuesOperation:
-        """Create the object values operation.
+@var_operation
+def object_keys_operation(value: ObjectVar):
+    """Get the keys of an object.
 
 
-        Args:
-            value: The value of the operation.
-            _var_data: Additional hooks and imports associated with the operation.
-
-        Returns:
-            The object values operation.
-        """
-        return cls(
-            _var_name="",
-            _var_type=List[value._value_type()],
-            _var_data=ImmutableVarData.merge(_var_data),
-            _value=value,
-        )
+    Args:
+        value: The object to get the keys from.
 
 
+    Returns:
+        The keys of the object.
+    """
+    return var_operation_return(
+        js_expression=f"Object.keys({value})",
+        var_type=List[str],
+    )
 
 
-class ObjectEntriesOperation(ObjectToArrayOperation):
-    """Operation to get the entries of an object."""
-
-    @cached_property_no_lock
-    def _cached_var_name(self) -> str:
-        """The name of the operation.
 
 
-        Returns:
-            The name of the operation.
-        """
-        return f"Object.entries({str(self._value)})"
+@var_operation
+def object_values_operation(value: ObjectVar):
+    """Get the values of an object.
 
 
-    @classmethod
-    def create(
-        cls,
-        value: ObjectVar,
-        _var_data: VarData | None = None,
-    ) -> ObjectEntriesOperation:
-        """Create the object entries operation.
+    Args:
+        value: The object to get the values from.
 
 
-        Args:
-            value: The value of the operation.
-            _var_data: Additional hooks and imports associated with the operation.
+    Returns:
+        The values of the object.
+    """
+    return var_operation_return(
+        js_expression=f"Object.values({value})",
+        var_type=List[value._value_type()],
+    )
 
 
-        Returns:
-            The object entries operation.
-        """
-        return cls(
-            _var_name="",
-            _var_type=List[Tuple[str, value._value_type()]],
-            _var_data=ImmutableVarData.merge(_var_data),
-            _value=value,
-        )
 
 
+@var_operation
+def object_entries_operation(value: ObjectVar):
+    """Get the entries of an object.
 
 
-@dataclasses.dataclass(
-    eq=False,
-    frozen=True,
-    **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class ObjectMergeOperation(CachedVarOperation, ObjectVar):
-    """Operation to merge two objects."""
+    Args:
+        value: The object to get the entries from.
 
 
-    _lhs: ObjectVar = dataclasses.field(
-        default_factory=lambda: LiteralObjectVar.create({})
-    )
-    _rhs: ObjectVar = dataclasses.field(
-        default_factory=lambda: LiteralObjectVar.create({})
+    Returns:
+        The entries of the object.
+    """
+    return var_operation_return(
+        js_expression=f"Object.entries({value})",
+        var_type=List[Tuple[str, value._value_type()]],
     )
     )
 
 
-    @cached_property_no_lock
-    def _cached_var_name(self) -> str:
-        """The name of the operation.
 
 
-        Returns:
-            The name of the operation.
-        """
-        return f"({{...{str(self._lhs)}, ...{str(self._rhs)}}})"
-
-    @classmethod
-    def create(
-        cls,
-        lhs: ObjectVar,
-        rhs: ObjectVar,
-        _var_data: VarData | None = None,
-    ) -> ObjectMergeOperation:
-        """Create the object merge operation.
+@var_operation
+def object_merge_operation(lhs: ObjectVar, rhs: ObjectVar):
+    """Merge two objects.
 
 
-        Args:
-            lhs: The left object to merge.
-            rhs: The right object to merge.
-            _var_data: Additional hooks and imports associated with the operation.
+    Args:
+        lhs: The first object to merge.
+        rhs: The second object to merge.
 
 
-        Returns:
-            The object merge operation.
-        """
-        # TODO: Figure out how to merge the types
-        return cls(
-            _var_name="",
-            _var_type=lhs._var_type,
-            _var_data=ImmutableVarData.merge(_var_data),
-            _lhs=lhs,
-            _rhs=rhs,
-        )
+    Returns:
+        The merged object.
+    """
+    return var_operation_return(
+        js_expression=f"({{...{lhs}, ...{rhs}}})",
+        var_type=Dict[
+            Union[lhs._key_type(), rhs._key_type()],
+            Union[lhs._value_type(), rhs._value_type()],
+        ],
+    )
 
 
 
 
 @dataclasses.dataclass(
 @dataclasses.dataclass(
@@ -688,49 +557,18 @@ class ToObjectOperation(CachedVarOperation, ObjectVar):
         )
         )
 
 
 
 
-@dataclasses.dataclass(
-    eq=False,
-    frozen=True,
-    **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class ObjectHasOwnProperty(CachedVarOperation, BooleanVar):
-    """Operation to check if an object has a property."""
-
-    _object: ObjectVar = dataclasses.field(
-        default_factory=lambda: LiteralObjectVar.create({})
-    )
-    _key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
-
-    @cached_property_no_lock
-    def _cached_var_name(self) -> str:
-        """The name of the operation.
-
-        Returns:
-            The name of the operation.
-        """
-        return f"{str(self._object)}.hasOwnProperty({str(self._key)})"
-
-    @classmethod
-    def create(
-        cls,
-        object: ObjectVar,
-        key: Var | Any,
-        _var_data: VarData | None = None,
-    ) -> ObjectHasOwnProperty:
-        """Create the object has own property operation.
+@var_operation
+def object_has_own_property_operation(object: ObjectVar, key: Var):
+    """Check if an object has a key.
 
 
-        Args:
-            object: The object to check.
-            key: The key to check.
-            _var_data: Additional hooks and imports associated with the operation.
+    Args:
+        object: The object to check.
+        key: The key to check.
 
 
-        Returns:
-            The object has own property operation.
-        """
-        return cls(
-            _var_name="",
-            _var_type=bool,
-            _var_data=ImmutableVarData.merge(_var_data),
-            _object=object,
-            _key=key if isinstance(key, Var) else LiteralVar.create(key),
-        )
+    Returns:
+        The result of the check.
+    """
+    return var_operation_return(
+        js_expression=f"{object}.hasOwnProperty({key})",
+        var_type=bool,
+    )

文件差异内容过多而无法显示
+ 225 - 628
reflex/ivars/sequence.py


+ 22 - 0
reflex/model.py

@@ -15,6 +15,7 @@ import alembic.runtime.environment
 import alembic.script
 import alembic.script
 import alembic.util
 import alembic.util
 import sqlalchemy
 import sqlalchemy
+import sqlalchemy.exc
 import sqlalchemy.orm
 import sqlalchemy.orm
 
 
 from reflex import constants
 from reflex import constants
@@ -51,6 +52,27 @@ def get_engine(url: str | None = None) -> sqlalchemy.engine.Engine:
     return sqlmodel.create_engine(url, echo=echo_db_query, connect_args=connect_args)
     return sqlmodel.create_engine(url, echo=echo_db_query, connect_args=connect_args)
 
 
 
 
+async def get_db_status() -> bool:
+    """Checks the status of the database connection.
+
+    Attempts to connect to the database and execute a simple query to verify connectivity.
+
+    Returns:
+        bool: The status of the database connection:
+            - True: The database is accessible.
+            - False: The database is not accessible.
+    """
+    status = True
+    try:
+        engine = get_engine()
+        with engine.connect() as connection:
+            connection.execute(sqlalchemy.text("SELECT 1"))
+    except sqlalchemy.exc.OperationalError:
+        status = False
+
+    return status
+
+
 SQLModelOrSqlAlchemy = Union[
 SQLModelOrSqlAlchemy = Union[
     Type[sqlmodel.SQLModel], Type[sqlalchemy.orm.DeclarativeBase]
     Type[sqlmodel.SQLModel], Type[sqlalchemy.orm.DeclarativeBase]
 ]
 ]

+ 4 - 0
reflex/reflex.py

@@ -16,6 +16,7 @@ from reflex_cli.utils import dependency
 from reflex import constants
 from reflex import constants
 from reflex.config import get_config
 from reflex.config import get_config
 from reflex.custom_components.custom_components import custom_components_cli
 from reflex.custom_components.custom_components import custom_components_cli
+from reflex.state import reset_disk_state_manager
 from reflex.utils import console, redir, telemetry
 from reflex.utils import console, redir, telemetry
 
 
 # Disable typer+rich integration for help panels
 # Disable typer+rich integration for help panels
@@ -180,6 +181,9 @@ def _run(
     if prerequisites.needs_reinit(frontend=frontend):
     if prerequisites.needs_reinit(frontend=frontend):
         _init(name=config.app_name, loglevel=loglevel)
         _init(name=config.app_name, loglevel=loglevel)
 
 
+    # Delete the states folder if it exists.
+    reset_disk_state_manager()
+
     # Find the next available open port if applicable.
     # Find the next available open port if applicable.
     if frontend:
     if frontend:
         frontend_port = processes.handle_port(
         frontend_port = processes.handle_port(

+ 48 - 31
reflex/state.py

@@ -431,8 +431,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         return [
         return [
             v
             v
             for mixin in cls._mixins() + [cls]
             for mixin in cls._mixins() + [cls]
-            for v in mixin.__dict__.values()
+            for name, v in mixin.__dict__.items()
             if isinstance(v, (ComputedVar, ImmutableComputedVar))
             if isinstance(v, (ComputedVar, ImmutableComputedVar))
+            and name not in cls.inherited_vars
         ]
         ]
 
 
     @classmethod
     @classmethod
@@ -494,21 +495,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             if cls.get_name() in set(
             if cls.get_name() in set(
                 c.get_name() for c in parent_state.class_subclasses
                 c.get_name() for c in parent_state.class_subclasses
             ):
             ):
-                if is_testing_env():
-                    # Clear existing subclass with same name when app is reloaded via
-                    # utils.prerequisites.get_app(reload=True)
-                    parent_state.class_subclasses = set(
-                        c
-                        for c in parent_state.class_subclasses
-                        if c.get_name() != cls.get_name()
-                    )
-                else:
-                    # During normal operation, subclasses cannot have the same name, even if they are
-                    # defined in different modules.
-                    raise StateValueError(
-                        f"The substate class '{cls.get_name()}' has been defined multiple times. "
-                        "Shadowing substate classes is not allowed."
-                    )
+                # This should not happen, since we have added module prefix to state names in #3214
+                raise StateValueError(
+                    f"The substate class '{cls.get_name()}' has been defined multiple times. "
+                    "Shadowing substate classes is not allowed."
+                )
             # Track this new subclass in the parent state's subclasses set.
             # Track this new subclass in the parent state's subclasses set.
             parent_state.class_subclasses.add(cls)
             parent_state.class_subclasses.add(cls)
 
 
@@ -560,6 +551,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
 
 
         for mixin in cls._mixins():
         for mixin in cls._mixins():
             for name, value in mixin.__dict__.items():
             for name, value in mixin.__dict__.items():
+                if name in cls.inherited_vars:
+                    continue
                 if isinstance(value, (ComputedVar, ImmutableComputedVar)):
                 if isinstance(value, (ComputedVar, ImmutableComputedVar)):
                     fget = cls._copy_fn(value.fget)
                     fget = cls._copy_fn(value.fget)
                     newcv = value._replace(
                     newcv = value._replace(
@@ -591,6 +584,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             cls.event_handlers[name] = handler
             cls.event_handlers[name] = handler
             setattr(cls, name, handler)
             setattr(cls, name, handler)
 
 
+        # Initialize per-class var dependency tracking.
+        cls._computed_var_dependencies = defaultdict(set)
+        cls._substate_var_dependencies = defaultdict(set)
         cls._init_var_dependency_dicts()
         cls._init_var_dependency_dicts()
 
 
     @staticmethod
     @staticmethod
@@ -660,10 +656,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         Additional updates tracking dicts for vars and substates that always
         Additional updates tracking dicts for vars and substates that always
         need to be recomputed.
         need to be recomputed.
         """
         """
-        # Initialize per-class var dependency tracking.
-        cls._computed_var_dependencies = defaultdict(set)
-        cls._substate_var_dependencies = defaultdict(set)
-
         inherited_vars = set(cls.inherited_vars).union(
         inherited_vars = set(cls.inherited_vars).union(
             set(cls.inherited_backend_vars),
             set(cls.inherited_backend_vars),
         )
         )
@@ -1013,20 +1005,20 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         Args:
         Args:
             args: a dict of args
             args: a dict of args
         """
         """
+        if not args:
+            return
 
 
         def argsingle_factory(param):
         def argsingle_factory(param):
-            @ComputedVar
             def inner_func(self) -> str:
             def inner_func(self) -> str:
                 return self.router.page.params.get(param, "")
                 return self.router.page.params.get(param, "")
 
 
-            return inner_func
+            return ComputedVar(fget=inner_func, cache=True)
 
 
         def arglist_factory(param):
         def arglist_factory(param):
-            @ComputedVar
             def inner_func(self) -> List:
             def inner_func(self) -> List:
                 return self.router.page.params.get(param, [])
                 return self.router.page.params.get(param, [])
 
 
-            return inner_func
+            return ComputedVar(fget=inner_func, cache=True)
 
 
         for param, value in args.items():
         for param, value in args.items():
             if value == constants.RouteArgType.SINGLE:
             if value == constants.RouteArgType.SINGLE:
@@ -1040,8 +1032,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             cls.vars[param] = cls.computed_vars[param] = func._var_set_state(cls)  # type: ignore
             cls.vars[param] = cls.computed_vars[param] = func._var_set_state(cls)  # type: ignore
             setattr(cls, param, func)
             setattr(cls, param, func)
 
 
-            # Reinitialize dependency tracking dicts.
-            cls._init_var_dependency_dicts()
+        # Reinitialize dependency tracking dicts.
+        cls._init_var_dependency_dicts()
 
 
     def __getattribute__(self, name: str) -> Any:
     def __getattribute__(self, name: str) -> Any:
         """Get the state var.
         """Get the state var.
@@ -1792,7 +1784,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             prop_name: self.get_value(getattr(self, prop_name))
             prop_name: self.get_value(getattr(self, prop_name))
             for prop_name in self.base_vars
             for prop_name in self.base_vars
         }
         }
-        if initial:
+        if initial and include_computed:
             computed_vars = {
             computed_vars = {
                 # Include initial computed vars.
                 # Include initial computed vars.
                 prop_name: (
                 prop_name: (
@@ -2295,11 +2287,12 @@ class StateProxy(wrapt.ObjectProxy):
         Returns:
         Returns:
             The state update.
             The state update.
         """
         """
+        original_mutable = self._self_mutable
         self._self_mutable = True
         self._self_mutable = True
         try:
         try:
             return self.__wrapped__._as_state_update(*args, **kwargs)
             return self.__wrapped__._as_state_update(*args, **kwargs)
         finally:
         finally:
-            self._self_mutable = False
+            self._self_mutable = original_mutable
 
 
 
 
 class StateUpdate(Base):
 class StateUpdate(Base):
@@ -2457,6 +2450,20 @@ def _default_token_expiration() -> int:
     return get_config().redis_token_expiration
     return get_config().redis_token_expiration
 
 
 
 
+def _serialize_type(type_: Any) -> str:
+    """Serialize a type.
+
+    Args:
+        type_: The type to serialize.
+
+    Returns:
+        The serialized type.
+    """
+    if not inspect.isclass(type_):
+        return f"{type_}"
+    return f"{type_.__module__}.{type_.__qualname__}"
+
+
 def state_to_schema(
 def state_to_schema(
     state: BaseState,
     state: BaseState,
 ) -> List[
 ) -> List[
@@ -2480,7 +2487,7 @@ def state_to_schema(
             (
             (
                 field_name,
                 field_name,
                 model_field.name,
                 model_field.name,
-                model_field.type_,
+                _serialize_type(model_field.type_),
                 (
                 (
                     model_field.required
                     model_field.required
                     if isinstance(model_field.required, bool)
                     if isinstance(model_field.required, bool)
@@ -2492,6 +2499,14 @@ def state_to_schema(
     )
     )
 
 
 
 
+def reset_disk_state_manager():
+    """Reset the disk state manager."""
+    states_directory = prerequisites.get_web_dir() / constants.Dirs.STATES
+    if states_directory.exists():
+        for path in states_directory.iterdir():
+            path.unlink()
+
+
 class StateManagerDisk(StateManager):
 class StateManagerDisk(StateManager):
     """A state manager that stores states in memory."""
     """A state manager that stores states in memory."""
 
 
@@ -2643,7 +2658,7 @@ class StateManagerDisk(StateManager):
 
 
         self.states[substate_token] = substate
         self.states[substate_token] = substate
 
 
-        state_dilled = dill.dumps((state_to_schema(substate), substate), byref=True)
+        state_dilled = dill.dumps((state_to_schema(substate), substate))
         if not self.states_directory.exists():
         if not self.states_directory.exists():
             self.states_directory.mkdir(parents=True, exist_ok=True)
             self.states_directory.mkdir(parents=True, exist_ok=True)
         self.token_path(substate_token).write_bytes(state_dilled)
         self.token_path(substate_token).write_bytes(state_dilled)
@@ -3592,5 +3607,7 @@ def reload_state_module(
         if subclass.__module__ == module and module is not None:
         if subclass.__module__ == module and module is not None:
             state.class_subclasses.remove(subclass)
             state.class_subclasses.remove(subclass)
             state._always_dirty_substates.discard(subclass.get_name())
             state._always_dirty_substates.discard(subclass.get_name())
-    state._init_var_dependency_dicts()
+            state._computed_var_dependencies = defaultdict(set)
+            state._substate_var_dependencies = defaultdict(set)
+            state._init_var_dependency_dicts()
     state.get_class_substate.cache_clear()
     state.get_class_substate.cache_clear()

+ 1 - 1
reflex/utils/path_ops.py

@@ -209,4 +209,4 @@ def find_replace(directory: str | Path, find: str, replace: str):
             filepath = Path(root, file)
             filepath = Path(root, file)
             text = filepath.read_text(encoding="utf-8")
             text = filepath.read_text(encoding="utf-8")
             text = re.sub(find, replace, text)
             text = re.sub(find, replace, text)
-            filepath.write_text(text)
+            filepath.write_text(text, encoding="utf-8")

+ 34 - 14
reflex/utils/prerequisites.py

@@ -28,6 +28,7 @@ import typer
 from alembic.util.exc import CommandError
 from alembic.util.exc import CommandError
 from packaging import version
 from packaging import version
 from redis import Redis as RedisSync
 from redis import Redis as RedisSync
+from redis import exceptions
 from redis.asyncio import Redis
 from redis.asyncio import Redis
 
 
 from reflex import constants, model
 from reflex import constants, model
@@ -324,24 +325,43 @@ def parse_redis_url() -> str | dict | None:
     """Parse the REDIS_URL in config if applicable.
     """Parse the REDIS_URL in config if applicable.
 
 
     Returns:
     Returns:
-        If redis-py syntax, return the URL as it is. Otherwise, return the host/port/db as a dict.
+        If url is non-empty, return the URL as it is.
+
+    Raises:
+        ValueError: If the REDIS_URL is not a supported scheme.
     """
     """
     config = get_config()
     config = get_config()
     if not config.redis_url:
     if not config.redis_url:
         return None
         return None
-    if config.redis_url.startswith(("redis://", "rediss://", "unix://")):
-        return config.redis_url
-    console.deprecate(
-        feature_name="host[:port] style redis urls",
-        reason="redis-py url syntax is now being used",
-        deprecation_version="0.3.6",
-        removal_version="0.6.0",
-    )
-    redis_url, has_port, redis_port = config.redis_url.partition(":")
-    if not has_port:
-        redis_port = 6379
-    console.info(f"Using redis at {config.redis_url}")
-    return dict(host=redis_url, port=int(redis_port), db=0)
+    if not config.redis_url.startswith(("redis://", "rediss://", "unix://")):
+        raise ValueError(
+            "REDIS_URL must start with 'redis://', 'rediss://', or 'unix://'."
+        )
+    return config.redis_url
+
+
+async def get_redis_status() -> bool | None:
+    """Checks the status of the Redis connection.
+
+    Attempts to connect to Redis and send a ping command to verify connectivity.
+
+    Returns:
+        bool or None: The status of the Redis connection:
+            - True: Redis is accessible and responding.
+            - False: Redis is not accessible due to a connection error.
+            - None: Redis not used i.e redis_url is not set in rxconfig.
+    """
+    try:
+        status = True
+        redis_client = get_redis_sync()
+        if redis_client is not None:
+            redis_client.ping()
+        else:
+            status = None
+    except exceptions.RedisError:
+        status = False
+
+    return status
 
 
 
 
 def validate_app_name(app_name: str | None = None) -> str:
 def validate_app_name(app_name: str | None = None) -> str:

+ 2 - 2
tests/components/core/test_colors.py

@@ -67,11 +67,11 @@ def test_color(color, expected):
     [
     [
         (
         (
             rx.cond(True, rx.color("mint"), rx.color("tomato", 5)),
             rx.cond(True, rx.color("mint"), rx.color("tomato", 5)),
-            '(Boolean(true) ? "var(--mint-7)" : "var(--tomato-5)")',
+            '(true ? "var(--mint-7)" : "var(--tomato-5)")',
         ),
         ),
         (
         (
             rx.cond(True, rx.color(ColorState.color), rx.color(ColorState.color, 5)),  # type: ignore
             rx.cond(True, rx.color(ColorState.color), rx.color(ColorState.color, 5)),  # type: ignore
-            f'(Boolean(true) ? ("var(--"+{str(color_state_name)}.color+"-7)") : ("var(--"+{str(color_state_name)}.color+"-5)"))',
+            f'(true ? ("var(--"+{str(color_state_name)}.color+"-7)") : ("var(--"+{str(color_state_name)}.color+"-5)"))',
         ),
         ),
         (
         (
             rx.match(
             rx.match(

+ 3 - 4
tests/components/core/test_cond.py

@@ -23,7 +23,7 @@ def cond_state(request):
 def test_f_string_cond_interpolation():
 def test_f_string_cond_interpolation():
     # make sure backticks inside interpolation don't get escaped
     # make sure backticks inside interpolation don't get escaped
     var = LiteralVar.create(f"x {cond(True, 'a', 'b')}")
     var = LiteralVar.create(f"x {cond(True, 'a', 'b')}")
-    assert str(var) == '("x "+(Boolean(true) ? "a" : "b"))'
+    assert str(var) == '("x "+(true ? "a" : "b"))'
 
 
 
 
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(
@@ -97,7 +97,7 @@ def test_prop_cond(c1: Any, c2: Any):
         c1 = json.dumps(c1)
         c1 = json.dumps(c1)
     if not isinstance(c2, Var):
     if not isinstance(c2, Var):
         c2 = json.dumps(c2)
         c2 = json.dumps(c2)
-    assert str(prop_cond) == f"(Boolean(true) ? {c1} : {c2})"
+    assert str(prop_cond) == f"(true ? {c1} : {c2})"
 
 
 
 
 def test_cond_no_mix():
 def test_cond_no_mix():
@@ -141,8 +141,7 @@ def test_cond_computed_var():
 
 
     state_name = format_state_name(CondStateComputed.get_full_name())
     state_name = format_state_name(CondStateComputed.get_full_name())
     assert (
     assert (
-        str(comp)
-        == f"(Boolean(true) ? {state_name}.computed_int : {state_name}.computed_str)"
+        str(comp) == f"(true ? {state_name}.computed_int : {state_name}.computed_str)"
     )
     )
 
 
     assert comp._var_type == Union[int, str]
     assert comp._var_type == Union[int, str]

+ 1 - 10
tests/test_app.py

@@ -906,7 +906,7 @@ class DynamicState(BaseState):
         """Increment the counter var."""
         """Increment the counter var."""
         self.counter = self.counter + 1
         self.counter = self.counter + 1
 
 
-    @computed_var
+    @computed_var(cache=True)
     def comp_dynamic(self) -> str:
     def comp_dynamic(self) -> str:
         """A computed var that depends on the dynamic var.
         """A computed var that depends on the dynamic var.
 
 
@@ -1049,9 +1049,6 @@ async def test_dynamic_route_var_route_change_completed_on_load(
         assert on_load_update == StateUpdate(
         assert on_load_update == StateUpdate(
             delta={
             delta={
                 state.get_name(): {
                 state.get_name(): {
-                    # These computed vars _shouldn't_ be here, because they didn't change
-                    arg_name: exp_val,
-                    f"comp_{arg_name}": exp_val,
                     "loaded": exp_index + 1,
                     "loaded": exp_index + 1,
                 },
                 },
             },
             },
@@ -1073,9 +1070,6 @@ async def test_dynamic_route_var_route_change_completed_on_load(
         assert on_set_is_hydrated_update == StateUpdate(
         assert on_set_is_hydrated_update == StateUpdate(
             delta={
             delta={
                 state.get_name(): {
                 state.get_name(): {
-                    # These computed vars _shouldn't_ be here, because they didn't change
-                    arg_name: exp_val,
-                    f"comp_{arg_name}": exp_val,
                     "is_hydrated": True,
                     "is_hydrated": True,
                 },
                 },
             },
             },
@@ -1097,9 +1091,6 @@ async def test_dynamic_route_var_route_change_completed_on_load(
         assert update == StateUpdate(
         assert update == StateUpdate(
             delta={
             delta={
                 state.get_name(): {
                 state.get_name(): {
-                    # These computed vars _shouldn't_ be here, because they didn't change
-                    f"comp_{arg_name}": exp_val,
-                    arg_name: exp_val,
                     "counter": exp_index + 1,
                     "counter": exp_index + 1,
                 }
                 }
             },
             },

+ 106 - 0
tests/test_health_endpoint.py

@@ -0,0 +1,106 @@
+import json
+from unittest.mock import MagicMock, Mock
+
+import pytest
+import sqlalchemy
+from redis.exceptions import RedisError
+
+from reflex.app import health
+from reflex.model import get_db_status
+from reflex.utils.prerequisites import get_redis_status
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize(
+    "mock_redis_client, expected_status",
+    [
+        # Case 1: Redis client is available and responds to ping
+        (Mock(ping=lambda: None), True),
+        # Case 2: Redis client raises RedisError
+        (Mock(ping=lambda: (_ for _ in ()).throw(RedisError)), False),
+        # Case 3: Redis client is not used
+        (None, None),
+    ],
+)
+async def test_get_redis_status(mock_redis_client, expected_status, mocker):
+    # Mock the `get_redis_sync` function to return the mock Redis client
+    mock_get_redis_sync = mocker.patch(
+        "reflex.utils.prerequisites.get_redis_sync", return_value=mock_redis_client
+    )
+
+    # Call the function
+    status = await get_redis_status()
+
+    # Verify the result
+    assert status == expected_status
+    mock_get_redis_sync.assert_called_once()
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize(
+    "mock_engine, execute_side_effect, expected_status",
+    [
+        # Case 1: Database is accessible
+        (MagicMock(), None, True),
+        # Case 2: Database connection error (OperationalError)
+        (
+            MagicMock(),
+            sqlalchemy.exc.OperationalError("error", "error", "error"),
+            False,
+        ),
+    ],
+)
+async def test_get_db_status(mock_engine, execute_side_effect, expected_status, mocker):
+    # Mock get_engine to return the mock_engine
+    mock_get_engine = mocker.patch("reflex.model.get_engine", return_value=mock_engine)
+
+    # Mock the connection and its execute method
+    if mock_engine:
+        mock_connection = mock_engine.connect.return_value.__enter__.return_value
+        if execute_side_effect:
+            # Simulate execute method raising an exception
+            mock_connection.execute.side_effect = execute_side_effect
+        else:
+            # Simulate successful execute call
+            mock_connection.execute.return_value = None
+
+    # Call the function
+    status = await get_db_status()
+
+    # Verify the result
+    assert status == expected_status
+    mock_get_engine.assert_called_once()
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize(
+    "db_status, redis_status, expected_status, expected_code",
+    [
+        # Case 1: Both services are connected
+        (True, True, {"status": True, "db": True, "redis": True}, 200),
+        # Case 2: Database not connected, Redis connected
+        (False, True, {"status": False, "db": False, "redis": True}, 503),
+        # Case 3: Database connected, Redis not connected
+        (True, False, {"status": False, "db": True, "redis": False}, 503),
+        # Case 4: Both services not connected
+        (False, False, {"status": False, "db": False, "redis": False}, 503),
+        # Case 5: Database Connected, Redis not used
+        (True, None, {"status": True, "db": True, "redis": False}, 200),
+    ],
+)
+async def test_health(db_status, redis_status, expected_status, expected_code, mocker):
+    # Mock get_db_status and get_redis_status
+    mocker.patch("reflex.app.get_db_status", return_value=db_status)
+    mocker.patch(
+        "reflex.utils.prerequisites.get_redis_status", return_value=redis_status
+    )
+
+    # Call the async health function
+    response = await health()
+
+    print(json.loads(response.body))
+    print(expected_status)
+
+    # Verify the response content and status code
+    assert response.status_code == expected_code
+    assert json.loads(response.body) == expected_status

+ 78 - 1
tests/test_state.py

@@ -649,7 +649,12 @@ def test_set_dirty_var(test_state):
     assert test_state.dirty_vars == set()
     assert test_state.dirty_vars == set()
 
 
 
 
-def test_set_dirty_substate(test_state, child_state, child_state2, grandchild_state):
+def test_set_dirty_substate(
+    test_state: TestState,
+    child_state: ChildState,
+    child_state2: ChildState2,
+    grandchild_state: GrandchildState,
+):
     """Test changing substate vars marks the value as dirty.
     """Test changing substate vars marks the value as dirty.
 
 
     Args:
     Args:
@@ -3077,6 +3082,51 @@ def test_potentially_dirty_substates():
     assert C1._potentially_dirty_substates() == set()
     assert C1._potentially_dirty_substates() == set()
 
 
 
 
+def test_router_var_dep() -> None:
+    """Test that router var dependencies are correctly tracked."""
+
+    class RouterVarParentState(State):
+        """A parent state for testing router var dependency."""
+
+        pass
+
+    class RouterVarDepState(RouterVarParentState):
+        """A state with a router var dependency."""
+
+        @rx.var(cache=True)
+        def foo(self) -> str:
+            return self.router.page.params.get("foo", "")
+
+    foo = RouterVarDepState.computed_vars["foo"]
+    State._init_var_dependency_dicts()
+
+    assert foo._deps(objclass=RouterVarDepState) == {"router"}
+    assert RouterVarParentState._potentially_dirty_substates() == {RouterVarDepState}
+    assert RouterVarParentState._substate_var_dependencies == {
+        "router": {RouterVarDepState.get_name()}
+    }
+    assert RouterVarDepState._computed_var_dependencies == {
+        "router": {"foo"},
+    }
+
+    rx_state = State()
+    parent_state = RouterVarParentState()
+    state = RouterVarDepState()
+
+    # link states
+    rx_state.substates = {RouterVarParentState.get_name(): parent_state}
+    parent_state.parent_state = rx_state
+    state.parent_state = parent_state
+    parent_state.substates = {RouterVarDepState.get_name(): state}
+
+    assert state.dirty_vars == set()
+
+    # Reassign router var
+    state.router = state.router
+    assert state.dirty_vars == {"foo", "router"}
+    assert parent_state.dirty_substates == {RouterVarDepState.get_name()}
+
+
 @pytest.mark.asyncio
 @pytest.mark.asyncio
 async def test_setvar(mock_app: rx.App, token: str):
 async def test_setvar(mock_app: rx.App, token: str):
     """Test that setvar works correctly.
     """Test that setvar works correctly.
@@ -3161,6 +3211,15 @@ class MixinState(State, mixin=True):
     num: int = 0
     num: int = 0
     _backend: int = 0
     _backend: int = 0
 
 
+    @rx.var(cache=True)
+    def computed(self) -> str:
+        """A computed var on mixin state.
+
+        Returns:
+            A computed value.
+        """
+        return ""
+
 
 
 class UsesMixinState(MixinState, State):
 class UsesMixinState(MixinState, State):
     """A state that uses the mixin state."""
     """A state that uses the mixin state."""
@@ -3168,8 +3227,26 @@ class UsesMixinState(MixinState, State):
     pass
     pass
 
 
 
 
+class ChildUsesMixinState(UsesMixinState):
+    """A child state that uses the mixin state."""
+
+    pass
+
+
 def test_mixin_state() -> None:
 def test_mixin_state() -> None:
     """Test that a mixin state works correctly."""
     """Test that a mixin state works correctly."""
     assert "num" in UsesMixinState.base_vars
     assert "num" in UsesMixinState.base_vars
     assert "num" in UsesMixinState.vars
     assert "num" in UsesMixinState.vars
     assert UsesMixinState.backend_vars == {"_backend": 0}
     assert UsesMixinState.backend_vars == {"_backend": 0}
+
+    assert "computed" in UsesMixinState.computed_vars
+    assert "computed" in UsesMixinState.vars
+
+
+def test_child_mixin_state() -> None:
+    """Test that mixin vars are only applied to the highest state in the hierarchy."""
+    assert "num" in ChildUsesMixinState.inherited_vars
+    assert "num" not in ChildUsesMixinState.base_vars
+
+    assert "computed" in ChildUsesMixinState.inherited_vars
+    assert "computed" not in ChildUsesMixinState.computed_vars

+ 7 - 6
tests/test_var.py

@@ -12,6 +12,7 @@ from reflex.ivars.base import (
     ImmutableVar,
     ImmutableVar,
     LiteralVar,
     LiteralVar,
     var_operation,
     var_operation,
+    var_operation_return,
 )
 )
 from reflex.ivars.function import ArgsFunctionOperation, FunctionStringVar
 from reflex.ivars.function import ArgsFunctionOperation, FunctionStringVar
 from reflex.ivars.number import (
 from reflex.ivars.number import (
@@ -925,9 +926,9 @@ def test_function_var():
 
 
 
 
 def test_var_operation():
 def test_var_operation():
-    @var_operation(output=NumberVar)
-    def add(a: Union[NumberVar, int], b: Union[NumberVar, int]) -> str:
-        return f"({a} + {b})"
+    @var_operation
+    def add(a: Union[NumberVar, int], b: Union[NumberVar, int]):
+        return var_operation_return(js_expression=f"({a} + {b})", var_type=int)
 
 
     assert str(add(1, 2)) == "(1 + 2)"
     assert str(add(1, 2)) == "(1 + 2)"
     assert str(add(a=4, b=-9)) == "(4 + -9)"
     assert str(add(a=4, b=-9)) == "(4 + -9)"
@@ -967,14 +968,14 @@ def test_all_number_operations():
 
 
     assert (
     assert (
         str(even_more_complicated_number)
         str(even_more_complicated_number)
-        == "!(Boolean((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) || (2 && Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))))))"
+        == "!(((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) || (2 && Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2)))) !== 0))"
     )
     )
 
 
     assert str(LiteralNumberVar.create(5) > False) == "(5 > 0)"
     assert str(LiteralNumberVar.create(5) > False) == "(5 > 0)"
-    assert str(LiteralBooleanVar.create(False) < 5) == "((false ? 1 : 0) < 5)"
+    assert str(LiteralBooleanVar.create(False) < 5) == "(Number(false) < 5)"
     assert (
     assert (
         str(LiteralBooleanVar.create(False) < LiteralBooleanVar.create(True))
         str(LiteralBooleanVar.create(False) < LiteralBooleanVar.create(True))
-        == "((false ? 1 : 0) < (true ? 1 : 0))"
+        == "(Number(false) < Number(true))"
     )
     )
 
 
 
 

部分文件因为文件数量过多而无法显示