Merge branch 'main' into maintenance/version_bump
This commit is contained in:
commit
83d7131f60
29
.github/workflows/integration_tests.yml
vendored
29
.github/workflows/integration_tests.yml
vendored
@ -162,7 +162,36 @@ jobs:
|
||||
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
|
||||
--pr-id "${{ github.event.pull_request.id }}" --branch-name "${{ github.head_ref || github.ref_name }}"
|
||||
--app-name "reflex-web" --path ./reflex-web/.web
|
||||
|
||||
rx-shout-from-template:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup_build_env
|
||||
with:
|
||||
python-version: '3.11.4'
|
||||
run-poetry-install: true
|
||||
create-venv-at-path: .venv
|
||||
- name: Create app directory
|
||||
run: mkdir rx-shout-from-template
|
||||
- name: Init reflex-web from template
|
||||
run: poetry run reflex init --template https://github.com/masenf/rx_shout
|
||||
working-directory: ./rx-shout-from-template
|
||||
- name: ignore reflex pin in requirements
|
||||
run: sed -i -e '/reflex==/d' requirements.txt
|
||||
working-directory: ./rx-shout-from-template
|
||||
- name: Install additional dependencies
|
||||
run: poetry run uv pip install -r requirements.txt
|
||||
working-directory: ./rx-shout-from-template
|
||||
- name: Run Website and Check for errors
|
||||
run: |
|
||||
# Check that npm is home
|
||||
npm -v
|
||||
poetry run bash scripts/integration.sh ./rx-shout-from-template prod
|
||||
|
||||
|
||||
reflex-web-macos:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
strategy:
|
||||
|
@ -23,7 +23,7 @@
|
||||
# for example, pass `docker build --platform=linux/amd64 ...`
|
||||
|
||||
# Stage 1: init
|
||||
FROM python:3.11 as init
|
||||
FROM python:3.13 as init
|
||||
|
||||
ARG uv=/root/.local/bin/uv
|
||||
|
||||
@ -48,7 +48,7 @@ RUN $uv pip install -r requirements.txt
|
||||
RUN reflex init
|
||||
|
||||
# Stage 2: copy artifacts into slim image
|
||||
FROM python:3.11-slim
|
||||
FROM python:3.13-slim
|
||||
WORKDIR /app
|
||||
RUN adduser --disabled-password --home /app reflex
|
||||
COPY --chown=reflex --from=init /app /app
|
||||
|
@ -2,7 +2,7 @@
|
||||
# instance of a Reflex app.
|
||||
|
||||
# Stage 1: init
|
||||
FROM python:3.11 as init
|
||||
FROM python:3.13 as init
|
||||
|
||||
ARG uv=/root/.local/bin/uv
|
||||
|
||||
@ -35,7 +35,7 @@ RUN rm -rf .web && mkdir .web
|
||||
RUN mv /tmp/_static .web/_static
|
||||
|
||||
# Stage 2: copy artifacts into slim image
|
||||
FROM python:3.11-slim
|
||||
FROM python:3.13-slim
|
||||
WORKDIR /app
|
||||
RUN adduser --disabled-password --home /app reflex
|
||||
COPY --chown=reflex --from=init /app /app
|
||||
|
3
docker-example/production-one-port/.dockerignore
Normal file
3
docker-example/production-one-port/.dockerignore
Normal file
@ -0,0 +1,3 @@
|
||||
.web
|
||||
!.web/bun.lockb
|
||||
!.web/package.json
|
14
docker-example/production-one-port/Caddyfile
Normal file
14
docker-example/production-one-port/Caddyfile
Normal file
@ -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
|
||||
}
|
62
docker-example/production-one-port/Dockerfile
Normal file
62
docker-example/production-one-port/Dockerfile
Normal file
@ -0,0 +1,62 @@
|
||||
# This Dockerfile is used to deploy a single-container Reflex app instance
|
||||
# to services like Render, Railway, Heroku, GCP, and others.
|
||||
|
||||
# If the service expects a different port, provide it here (f.e Render expects port 10000)
|
||||
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.
|
||||
ARG API_URL
|
||||
|
||||
# It uses a reverse proxy to serve the frontend statically and proxy to backend
|
||||
# from a single exposed port, expecting TLS termination to be handled at the
|
||||
# edge by the given platform.
|
||||
FROM python:3.13 as builder
|
||||
|
||||
RUN mkdir -p /app/.web
|
||||
RUN python -m venv /app/.venv
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install python app requirements and reflex in the container
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# Install reflex helper utilities like bun/fnm/node
|
||||
COPY rxconfig.py ./
|
||||
RUN reflex init
|
||||
|
||||
# Install pre-cached frontend dependencies (if exist)
|
||||
COPY *.web/bun.lockb *.web/package.json .web/
|
||||
RUN if [ -f .web/bun.lockb ]; then cd .web && ~/.local/share/reflex/bun/bin/bun install --frozen-lockfile; fi
|
||||
|
||||
# Copy local context to `/app` inside container (see .dockerignore)
|
||||
COPY . .
|
||||
|
||||
ARG PORT API_URL
|
||||
# Download other npm dependencies and compile frontend
|
||||
RUN API_URL=${API_URL:-http://localhost:$PORT} reflex export --loglevel debug --frontend-only --no-zip && mv .web/_static/* /srv/ && rm -rf .web
|
||||
|
||||
|
||||
# Final image with only necessary files
|
||||
FROM python:3.13-slim
|
||||
|
||||
# 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/*
|
||||
|
||||
ARG PORT API_URL
|
||||
ENV PATH="/app/.venv/bin:$PATH" PORT=$PORT API_URL=${API_URL:-http://localhost:$PORT} REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app /app
|
||||
COPY --from=builder /srv /srv
|
||||
|
||||
# Needed until Reflex properly passes SIGTERM on backend.
|
||||
STOPSIGNAL SIGKILL
|
||||
|
||||
EXPOSE $PORT
|
||||
|
||||
# Apply migrations before starting the backend.
|
||||
CMD [ -d alembic ] && reflex db migrate; \
|
||||
caddy start && \
|
||||
redis-server --daemonize yes && \
|
||||
exec reflex run --env prod --backend-only
|
37
docker-example/production-one-port/README.md
Normal file
37
docker-example/production-one-port/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# production-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.
|
||||
|
||||
Conceptually it is similar to the `simple-one-port` example except it:
|
||||
* has layer caching for python, reflex, and node dependencies
|
||||
* uses multi-stage build to reduce the size of the final image
|
||||
|
||||
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.
|
||||
|
||||
## Build
|
||||
|
||||
```console
|
||||
docker build -t reflex-production-one-port .
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```console
|
||||
docker run -p 8080:8080 reflex-production-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.
|
@ -11,4 +11,4 @@ root * /srv
|
||||
route {
|
||||
try_files {path} {path}/ /404.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
# It uses a reverse proxy to serve the frontend statically and proxy to backend
|
||||
# from a single exposed port, expecting TLS termination to be handled at the
|
||||
# edge by the given platform.
|
||||
FROM python:3.11
|
||||
FROM python:3.13
|
||||
|
||||
# If the service expects a different port, provide it here (f.e Render expects port 10000)
|
||||
ARG PORT=8080
|
||||
@ -38,4 +38,4 @@ EXPOSE $PORT
|
||||
CMD [ -d alembic ] && reflex db migrate; \
|
||||
caddy start && \
|
||||
redis-server --daemonize yes && \
|
||||
exec reflex run --env prod --backend-only
|
||||
exec reflex run --env prod --backend-only
|
||||
|
@ -1,5 +1,5 @@
|
||||
# This Dockerfile is used to deploy a simple single-container Reflex app instance.
|
||||
FROM python:3.12
|
||||
FROM python:3.13
|
||||
|
||||
RUN apt-get update && apt-get install -y redis-server && rm -rf /var/lib/apt/lists/*
|
||||
ENV REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
|
||||
|
132
poetry.lock
generated
132
poetry.lock
generated
@ -760,13 +760,13 @@ trio = ["trio (>=0.22.0,<1.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.0"
|
||||
version = "0.28.1"
|
||||
description = "The next generation HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc"},
|
||||
{file = "httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0"},
|
||||
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
|
||||
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -984,13 +984,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "mako"
|
||||
version = "1.3.7"
|
||||
version = "1.3.8"
|
||||
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "Mako-1.3.7-py3-none-any.whl", hash = "sha256:d18f990ad57f800ce8e76cbfb0b74afe471c293517e9f5003ace6dad5aa72c36"},
|
||||
{file = "mako-1.3.7.tar.gz", hash = "sha256:20405b1232e0759f0e7d87b01f6bb94fce0761747f1cb876ecf90bd512d0b639"},
|
||||
{file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627"},
|
||||
{file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1216,66 +1216,66 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.1.3"
|
||||
version = "2.2.0"
|
||||
description = "Fundamental package for array computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "numpy-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff"},
|
||||
{file = "numpy-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5"},
|
||||
{file = "numpy-2.1.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1"},
|
||||
{file = "numpy-2.1.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd"},
|
||||
{file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3"},
|
||||
{file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098"},
|
||||
{file = "numpy-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c"},
|
||||
{file = "numpy-2.1.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4"},
|
||||
{file = "numpy-2.1.3-cp310-cp310-win32.whl", hash = "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23"},
|
||||
{file = "numpy-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0"},
|
||||
{file = "numpy-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d"},
|
||||
{file = "numpy-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41"},
|
||||
{file = "numpy-2.1.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9"},
|
||||
{file = "numpy-2.1.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09"},
|
||||
{file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a"},
|
||||
{file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b"},
|
||||
{file = "numpy-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee"},
|
||||
{file = "numpy-2.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0"},
|
||||
{file = "numpy-2.1.3-cp311-cp311-win32.whl", hash = "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9"},
|
||||
{file = "numpy-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2"},
|
||||
{file = "numpy-2.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e"},
|
||||
{file = "numpy-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958"},
|
||||
{file = "numpy-2.1.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8"},
|
||||
{file = "numpy-2.1.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564"},
|
||||
{file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512"},
|
||||
{file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b"},
|
||||
{file = "numpy-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc"},
|
||||
{file = "numpy-2.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0"},
|
||||
{file = "numpy-2.1.3-cp312-cp312-win32.whl", hash = "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9"},
|
||||
{file = "numpy-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a"},
|
||||
{file = "numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f"},
|
||||
{file = "numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598"},
|
||||
{file = "numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57"},
|
||||
{file = "numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe"},
|
||||
{file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43"},
|
||||
{file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56"},
|
||||
{file = "numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a"},
|
||||
{file = "numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef"},
|
||||
{file = "numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f"},
|
||||
{file = "numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed"},
|
||||
{file = "numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f"},
|
||||
{file = "numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4"},
|
||||
{file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e"},
|
||||
{file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0"},
|
||||
{file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408"},
|
||||
{file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6"},
|
||||
{file = "numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f"},
|
||||
{file = "numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17"},
|
||||
{file = "numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48"},
|
||||
{file = "numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4"},
|
||||
{file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f"},
|
||||
{file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4"},
|
||||
{file = "numpy-2.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d"},
|
||||
{file = "numpy-2.1.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb"},
|
||||
{file = "numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761"},
|
||||
{file = "numpy-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e25507d85da11ff5066269d0bd25d06e0a0f2e908415534f3e603d2a78e4ffa"},
|
||||
{file = "numpy-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a62eb442011776e4036af5c8b1a00b706c5bc02dc15eb5344b0c750428c94219"},
|
||||
{file = "numpy-2.2.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:b606b1aaf802e6468c2608c65ff7ece53eae1a6874b3765f69b8ceb20c5fa78e"},
|
||||
{file = "numpy-2.2.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:36b2b43146f646642b425dd2027730f99bac962618ec2052932157e213a040e9"},
|
||||
{file = "numpy-2.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fe8f3583e0607ad4e43a954e35c1748b553bfe9fdac8635c02058023277d1b3"},
|
||||
{file = "numpy-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:122fd2fcfafdefc889c64ad99c228d5a1f9692c3a83f56c292618a59aa60ae83"},
|
||||
{file = "numpy-2.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3f2f5cddeaa4424a0a118924b988746db6ffa8565e5829b1841a8a3bd73eb59a"},
|
||||
{file = "numpy-2.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fe4bb0695fe986a9e4deec3b6857003b4cfe5c5e4aac0b95f6a658c14635e31"},
|
||||
{file = "numpy-2.2.0-cp310-cp310-win32.whl", hash = "sha256:b30042fe92dbd79f1ba7f6898fada10bdaad1847c44f2dff9a16147e00a93661"},
|
||||
{file = "numpy-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dc1d6d66f8d37843ed281773c7174f03bf7ad826523f73435deb88ba60d2d4"},
|
||||
{file = "numpy-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9874bc2ff574c40ab7a5cbb7464bf9b045d617e36754a7bc93f933d52bd9ffc6"},
|
||||
{file = "numpy-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0da8495970f6b101ddd0c38ace92edea30e7e12b9a926b57f5fabb1ecc25bb90"},
|
||||
{file = "numpy-2.2.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0557eebc699c1c34cccdd8c3778c9294e8196df27d713706895edc6f57d29608"},
|
||||
{file = "numpy-2.2.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:3579eaeb5e07f3ded59298ce22b65f877a86ba8e9fe701f5576c99bb17c283da"},
|
||||
{file = "numpy-2.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40deb10198bbaa531509aad0cd2f9fadb26c8b94070831e2208e7df543562b74"},
|
||||
{file = "numpy-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2aed8fcf8abc3020d6a9ccb31dbc9e7d7819c56a348cc88fd44be269b37427e"},
|
||||
{file = "numpy-2.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a222d764352c773aa5ebde02dd84dba3279c81c6db2e482d62a3fa54e5ece69b"},
|
||||
{file = "numpy-2.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4e58666988605e251d42c2818c7d3d8991555381be26399303053b58a5bbf30d"},
|
||||
{file = "numpy-2.2.0-cp311-cp311-win32.whl", hash = "sha256:4723a50e1523e1de4fccd1b9a6dcea750c2102461e9a02b2ac55ffeae09a4410"},
|
||||
{file = "numpy-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:16757cf28621e43e252c560d25b15f18a2f11da94fea344bf26c599b9cf54b73"},
|
||||
{file = "numpy-2.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cff210198bb4cae3f3c100444c5eaa573a823f05c253e7188e1362a5555235b3"},
|
||||
{file = "numpy-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b92a5828bd4d9aa0952492b7de803135038de47343b2aa3cc23f3b71a3dc4e"},
|
||||
{file = "numpy-2.2.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ebe5e59545401fbb1b24da76f006ab19734ae71e703cdb4a8b347e84a0cece67"},
|
||||
{file = "numpy-2.2.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e2b8cd48a9942ed3f85b95ca4105c45758438c7ed28fff1e4ce3e57c3b589d8e"},
|
||||
{file = "numpy-2.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57fcc997ffc0bef234b8875a54d4058afa92b0b0c4223fc1f62f24b3b5e86038"},
|
||||
{file = "numpy-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ad7d11b309bd132d74397fcf2920933c9d1dc865487128f5c03d580f2c3d03"},
|
||||
{file = "numpy-2.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cb24cca1968b21355cc6f3da1a20cd1cebd8a023e3c5b09b432444617949085a"},
|
||||
{file = "numpy-2.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0798b138c291d792f8ea40fe3768610f3c7dd2574389e37c3f26573757c8f7ef"},
|
||||
{file = "numpy-2.2.0-cp312-cp312-win32.whl", hash = "sha256:afe8fb968743d40435c3827632fd36c5fbde633b0423da7692e426529b1759b1"},
|
||||
{file = "numpy-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:3a4199f519e57d517ebd48cb76b36c82da0360781c6a0353e64c0cac30ecaad3"},
|
||||
{file = "numpy-2.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f8c8b141ef9699ae777c6278b52c706b653bf15d135d302754f6b2e90eb30367"},
|
||||
{file = "numpy-2.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f0986e917aca18f7a567b812ef7ca9391288e2acb7a4308aa9d265bd724bdae"},
|
||||
{file = "numpy-2.2.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:1c92113619f7b272838b8d6702a7f8ebe5edea0df48166c47929611d0b4dea69"},
|
||||
{file = "numpy-2.2.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5a145e956b374e72ad1dff82779177d4a3c62bc8248f41b80cb5122e68f22d13"},
|
||||
{file = "numpy-2.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18142b497d70a34b01642b9feabb70156311b326fdddd875a9981f34a369b671"},
|
||||
{file = "numpy-2.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7d41d1612c1a82b64697e894b75db6758d4f21c3ec069d841e60ebe54b5b571"},
|
||||
{file = "numpy-2.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a98f6f20465e7618c83252c02041517bd2f7ea29be5378f09667a8f654a5918d"},
|
||||
{file = "numpy-2.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e09d40edfdb4e260cb1567d8ae770ccf3b8b7e9f0d9b5c2a9992696b30ce2742"},
|
||||
{file = "numpy-2.2.0-cp313-cp313-win32.whl", hash = "sha256:3905a5fffcc23e597ee4d9fb3fcd209bd658c352657548db7316e810ca80458e"},
|
||||
{file = "numpy-2.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a184288538e6ad699cbe6b24859206e38ce5fba28f3bcfa51c90d0502c1582b2"},
|
||||
{file = "numpy-2.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7832f9e8eb00be32f15fdfb9a981d6955ea9adc8574c521d48710171b6c55e95"},
|
||||
{file = "numpy-2.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0dd071b95bbca244f4cb7f70b77d2ff3aaaba7fa16dc41f58d14854a6204e6c"},
|
||||
{file = "numpy-2.2.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0b227dcff8cdc3efbce66d4e50891f04d0a387cce282fe1e66199146a6a8fca"},
|
||||
{file = "numpy-2.2.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ab153263a7c5ccaf6dfe7e53447b74f77789f28ecb278c3b5d49db7ece10d6d"},
|
||||
{file = "numpy-2.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e500aba968a48e9019e42c0c199b7ec0696a97fa69037bea163b55398e390529"},
|
||||
{file = "numpy-2.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:440cfb3db4c5029775803794f8638fbdbf71ec702caf32735f53b008e1eaece3"},
|
||||
{file = "numpy-2.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a55dc7a7f0b6198b07ec0cd445fbb98b05234e8b00c5ac4874a63372ba98d4ab"},
|
||||
{file = "numpy-2.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4bddbaa30d78c86329b26bd6aaaea06b1e47444da99eddac7bf1e2fab717bd72"},
|
||||
{file = "numpy-2.2.0-cp313-cp313t-win32.whl", hash = "sha256:30bf971c12e4365153afb31fc73f441d4da157153f3400b82db32d04de1e4066"},
|
||||
{file = "numpy-2.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d35717333b39d1b6bb8433fa758a55f1081543de527171543a2b710551d40881"},
|
||||
{file = "numpy-2.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e12c6c1ce84628c52d6367863773f7c8c8241be554e8b79686e91a43f1733773"},
|
||||
{file = "numpy-2.2.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:b6207dc8fb3c8cb5668e885cef9ec7f70189bec4e276f0ff70d5aa078d32c88e"},
|
||||
{file = "numpy-2.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a50aeff71d0f97b6450d33940c7181b08be1441c6c193e678211bff11aa725e7"},
|
||||
{file = "numpy-2.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:df12a1f99b99f569a7c2ae59aa2d31724e8d835fc7f33e14f4792e3071d11221"},
|
||||
{file = "numpy-2.2.0.tar.gz", hash = "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2212,13 +2212,13 @@ reflex = ">=0.6.0a"
|
||||
|
||||
[[package]]
|
||||
name = "reflex-hosting-cli"
|
||||
version = "0.1.29"
|
||||
version = "0.1.30"
|
||||
description = "Reflex Hosting CLI"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8"
|
||||
files = [
|
||||
{file = "reflex_hosting_cli-0.1.29-py3-none-any.whl", hash = "sha256:fcbdad829762287f32397cd8a5d46536ab0db396e7fdb8a23c7f9343d7dc8de0"},
|
||||
{file = "reflex_hosting_cli-0.1.29.tar.gz", hash = "sha256:7b421fec6936c26549c8c65c9dda34fc042eaaec79b238dce6b9c020f848563b"},
|
||||
{file = "reflex_hosting_cli-0.1.30-py3-none-any.whl", hash = "sha256:778c98d635003d8668158c22eaa0f7124d2bac92c8a1aabaed710960ca97796e"},
|
||||
{file = "reflex_hosting_cli-0.1.30.tar.gz", hash = "sha256:a0fdc73e595e6b9fd661e1307ae37267fb3815cc457b7f15938ba921c12fc0b6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3076,4 +3076,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "8fa53c85eef5591757969f94037d9295b7a00a3175c0766df426668d710afe30"
|
||||
content-hash = "d62cd1897d8f73e9aad9e907beb82be509dc5e33d8f37b36ebf26ad1f3075a9f"
|
||||
|
@ -49,7 +49,7 @@ wrapt = [
|
||||
{version = ">=1.11.0,<2.0", python = "<3.11"},
|
||||
]
|
||||
packaging = ">=23.1,<25.0"
|
||||
reflex-hosting-cli = ">=0.1.17,<2.0"
|
||||
reflex-hosting-cli = ">=0.1.29,<2.0"
|
||||
charset-normalizer = ">=3.3.2,<4.0"
|
||||
wheel = ">=0.42.0,<1.0"
|
||||
build = ">=1.0.3,<2.0"
|
||||
|
@ -457,7 +457,7 @@ export const connect = async (
|
||||
socket.current.on("reload", async (event) => {
|
||||
event_processing = false;
|
||||
queueEvents([...initialEvents(), JSON5.parse(event)], socket);
|
||||
})
|
||||
});
|
||||
|
||||
document.addEventListener("visibilitychange", checkVisibility);
|
||||
};
|
||||
@ -490,23 +490,30 @@ export const uploadFiles = async (
|
||||
return false;
|
||||
}
|
||||
|
||||
// Track how many partial updates have been processed for this upload.
|
||||
let resp_idx = 0;
|
||||
const eventHandler = (progressEvent) => {
|
||||
// handle any delta / event streamed from the upload event handler
|
||||
const event_callbacks = socket._callbacks.$event;
|
||||
// Whenever called, responseText will contain the entire response so far.
|
||||
const chunks = progressEvent.event.target.responseText.trim().split("\n");
|
||||
// So only process _new_ chunks beyond resp_idx.
|
||||
chunks.slice(resp_idx).map((chunk) => {
|
||||
try {
|
||||
socket._callbacks.$event.map((f) => {
|
||||
f(chunk);
|
||||
});
|
||||
resp_idx += 1;
|
||||
} catch (e) {
|
||||
if (progressEvent.progress === 1) {
|
||||
// Chunk may be incomplete, so only report errors when full response is available.
|
||||
console.log("Error parsing chunk", chunk, e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
event_callbacks.map((f, ix) => {
|
||||
f(chunk)
|
||||
.then(() => {
|
||||
if (ix === event_callbacks.length - 1) {
|
||||
// Mark this chunk as processed.
|
||||
resp_idx += 1;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
if (progressEvent.progress === 1) {
|
||||
// Chunk may be incomplete, so only report errors when full response is available.
|
||||
console.log("Error parsing chunk", chunk, e);
|
||||
}
|
||||
return;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -711,7 +718,7 @@ export const useEventLoop = (
|
||||
const combined_name = events.map((e) => e.name).join("+++");
|
||||
if (event_actions?.temporal) {
|
||||
if (!socket.current || !socket.current.connected) {
|
||||
return; // don't queue when the backend is not connected
|
||||
return; // don't queue when the backend is not connected
|
||||
}
|
||||
}
|
||||
if (event_actions?.throttle) {
|
||||
@ -852,7 +859,7 @@ export const useEventLoop = (
|
||||
if (router.components[router.pathname].error) {
|
||||
delete router.components[router.pathname].error;
|
||||
}
|
||||
}
|
||||
};
|
||||
router.events.on("routeChangeStart", change_start);
|
||||
router.events.on("routeChangeComplete", change_complete);
|
||||
router.events.on("routeChangeError", change_error);
|
||||
|
@ -293,13 +293,15 @@ class Upload(MemoizationLeaf):
|
||||
format.to_camel_case(key): value for key, value in upload_props.items()
|
||||
}
|
||||
|
||||
use_dropzone_arguments = {
|
||||
"onDrop": event_var,
|
||||
**upload_props,
|
||||
}
|
||||
use_dropzone_arguments = Var.create(
|
||||
{
|
||||
"onDrop": event_var,
|
||||
**upload_props,
|
||||
}
|
||||
)
|
||||
|
||||
left_side = f"const {{getRootProps: {root_props_unique_name}, getInputProps: {input_props_unique_name}}} "
|
||||
right_side = f"useDropzone({str(Var.create(use_dropzone_arguments))})"
|
||||
right_side = f"useDropzone({str(use_dropzone_arguments)})"
|
||||
|
||||
var_data = VarData.merge(
|
||||
VarData(
|
||||
@ -307,6 +309,7 @@ class Upload(MemoizationLeaf):
|
||||
hooks={Hooks.EVENTS: None},
|
||||
),
|
||||
event_var._get_all_var_data(),
|
||||
use_dropzone_arguments._get_all_var_data(),
|
||||
VarData(
|
||||
hooks={
|
||||
callback_str: None,
|
||||
|
@ -570,6 +570,9 @@ class Textarea(BaseHTML):
|
||||
# Visible width of the text control, in average character widths
|
||||
cols: Var[Union[str, int, bool]]
|
||||
|
||||
# The default value of the textarea when initially rendered
|
||||
default_value: Var[str]
|
||||
|
||||
# Name part of the textarea to submit in 'dir' and 'name' pair when form is submitted
|
||||
dirname: Var[Union[str, int, bool]]
|
||||
|
||||
|
@ -1350,6 +1350,7 @@ class Textarea(BaseHTML):
|
||||
auto_focus: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
auto_height: Optional[Union[Var[bool], bool]] = None,
|
||||
cols: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
default_value: Optional[Union[Var[str], str]] = None,
|
||||
dirname: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
disabled: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
enter_key_submit: Optional[Union[Var[bool], bool]] = None,
|
||||
@ -1439,6 +1440,7 @@ class Textarea(BaseHTML):
|
||||
auto_focus: Automatically focuses the textarea when the page loads
|
||||
auto_height: Automatically fit the content height to the text (use min-height with this prop)
|
||||
cols: Visible width of the text control, in average character widths
|
||||
default_value: The default value of the textarea when initially rendered
|
||||
dirname: Name part of the textarea to submit in 'dir' and 'name' pair when form is submitted
|
||||
disabled: Disables the textarea
|
||||
enter_key_submit: Enter key submits form (shift-enter adds new line)
|
||||
|
@ -41,6 +41,9 @@ class TextArea(RadixThemesComponent, elements.Textarea):
|
||||
# Automatically focuses the textarea when the page loads
|
||||
auto_focus: Var[bool]
|
||||
|
||||
# The default value of the textarea when initially rendered
|
||||
default_value: Var[str]
|
||||
|
||||
# Name part of the textarea to submit in 'dir' and 'name' pair when form is submitted
|
||||
dirname: Var[str]
|
||||
|
||||
|
@ -123,6 +123,7 @@ class TextArea(RadixThemesComponent, elements.Textarea):
|
||||
] = None,
|
||||
auto_complete: Optional[Union[Var[bool], bool]] = None,
|
||||
auto_focus: Optional[Union[Var[bool], bool]] = None,
|
||||
default_value: Optional[Union[Var[str], str]] = None,
|
||||
dirname: Optional[Union[Var[str], str]] = None,
|
||||
disabled: Optional[Union[Var[bool], bool]] = None,
|
||||
form: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
@ -217,6 +218,7 @@ class TextArea(RadixThemesComponent, elements.Textarea):
|
||||
radius: The radius of the text area: "none" | "small" | "medium" | "large" | "full"
|
||||
auto_complete: Whether the form control should have autocomplete enabled
|
||||
auto_focus: Automatically focuses the textarea when the page loads
|
||||
default_value: The default value of the textarea when initially rendered
|
||||
dirname: Name part of the textarea to submit in 'dir' and 'name' pair when form is submitted
|
||||
disabled: Disables the textarea
|
||||
form: Associates the textarea with a form (by id)
|
||||
|
@ -3,7 +3,6 @@
|
||||
from typing import Dict, Literal
|
||||
|
||||
from reflex.components.component import Component, MemoizationLeaf, NoSSRComponent
|
||||
from reflex.utils import console
|
||||
|
||||
|
||||
class Recharts(Component):
|
||||
@ -11,19 +10,8 @@ class Recharts(Component):
|
||||
|
||||
library = "recharts@2.13.0"
|
||||
|
||||
def render(self) -> Dict:
|
||||
"""Render the tag.
|
||||
|
||||
Returns:
|
||||
The rendered tag.
|
||||
"""
|
||||
tag = super().render()
|
||||
if any(p.startswith("css") for p in tag["props"]):
|
||||
console.warn(
|
||||
f"CSS props do not work for {self.__class__.__name__}. Consult docs to style it with its own prop."
|
||||
)
|
||||
tag["props"] = [p for p in tag["props"] if not p.startswith("css")]
|
||||
return tag
|
||||
def _get_style(self) -> Dict:
|
||||
return {"wrapperStyle": self.style}
|
||||
|
||||
|
||||
class RechartsCharts(NoSSRComponent, MemoizationLeaf):
|
||||
|
@ -11,7 +11,6 @@ from reflex.style import Style
|
||||
from reflex.vars.base import Var
|
||||
|
||||
class Recharts(Component):
|
||||
def render(self) -> Dict: ...
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # type: ignore
|
||||
|
@ -652,9 +652,9 @@ class Config(Base):
|
||||
frontend_packages: List[str] = []
|
||||
|
||||
# The hosting service backend URL.
|
||||
cp_backend_url: str = Hosting.CP_BACKEND_URL
|
||||
cp_backend_url: str = Hosting.HOSTING_SERVICE
|
||||
# The hosting service frontend URL.
|
||||
cp_web_url: str = Hosting.CP_WEB_URL
|
||||
cp_web_url: str = Hosting.HOSTING_SERVICE_UI
|
||||
|
||||
# The worker class used in production mode
|
||||
gunicorn_worker_class: str = "uvicorn.workers.UvicornH11Worker"
|
||||
|
@ -827,11 +827,11 @@ def _collect_details_for_gallery():
|
||||
Raises:
|
||||
Exit: If pyproject.toml file is ill-formed or the request to the backend services fails.
|
||||
"""
|
||||
from reflex.reflex import _login
|
||||
from reflex_cli.utils import hosting
|
||||
|
||||
console.rule("[bold]Authentication with Reflex Services")
|
||||
console.print("First let's log in to Reflex backend services.")
|
||||
access_token = _login()
|
||||
access_token, _ = hosting.authenticated_token()
|
||||
|
||||
console.rule("[bold]Custom Component Information")
|
||||
params = {}
|
||||
|
205
reflex/reflex.py
205
reflex/reflex.py
@ -9,8 +9,6 @@ from typing import List, Optional
|
||||
|
||||
import typer
|
||||
import typer.core
|
||||
from reflex_cli.deployments import deployments_cli
|
||||
from reflex_cli.utils import dependency
|
||||
from reflex_cli.v2.deployments import check_version, hosting_cli
|
||||
|
||||
from reflex import constants
|
||||
@ -330,47 +328,16 @@ def export(
|
||||
)
|
||||
|
||||
|
||||
def _login() -> str:
|
||||
"""Helper function to authenticate with Reflex hosting service."""
|
||||
from reflex_cli.utils import hosting
|
||||
|
||||
access_token, invitation_code = hosting.authenticated_token()
|
||||
if access_token:
|
||||
console.print("You already logged in.")
|
||||
return access_token
|
||||
|
||||
# If not already logged in, open a browser window/tab to the login page.
|
||||
access_token = hosting.authenticate_on_browser(invitation_code)
|
||||
|
||||
if not access_token:
|
||||
console.error("Unable to authenticate. Please try again or contact support.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print("Successfully logged in.")
|
||||
return access_token
|
||||
|
||||
|
||||
@cli.command()
|
||||
def login(
|
||||
loglevel: constants.LogLevel = typer.Option(
|
||||
config.loglevel, help="The log level to use."
|
||||
),
|
||||
):
|
||||
"""Authenticate with Reflex hosting service."""
|
||||
# Set the log level.
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
_login()
|
||||
|
||||
|
||||
@cli.command()
|
||||
def loginv2(loglevel: constants.LogLevel = typer.Option(config.loglevel)):
|
||||
def login(loglevel: constants.LogLevel = typer.Option(config.loglevel)):
|
||||
"""Authenicate with experimental Reflex hosting service."""
|
||||
from reflex_cli.v2 import cli as hosting_cli
|
||||
|
||||
check_version()
|
||||
|
||||
hosting_cli.login()
|
||||
validated_info = hosting_cli.login()
|
||||
if validated_info is not None:
|
||||
telemetry.send("login", user_uuid=validated_info.get("user_id"))
|
||||
|
||||
|
||||
@cli.command()
|
||||
@ -380,31 +347,11 @@ def logout(
|
||||
),
|
||||
):
|
||||
"""Log out of access to Reflex hosting service."""
|
||||
from reflex_cli.utils import hosting
|
||||
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
hosting.log_out_on_browser()
|
||||
console.debug("Deleting access token from config locally")
|
||||
hosting.delete_token_from_config(include_invitation_code=True)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def logoutv2(
|
||||
loglevel: constants.LogLevel = typer.Option(
|
||||
config.loglevel, help="The log level to use."
|
||||
),
|
||||
):
|
||||
"""Log out of access to Reflex hosting service."""
|
||||
from reflex_cli.v2.utils import hosting
|
||||
from reflex_cli.v2.cli import logout
|
||||
|
||||
check_version()
|
||||
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
hosting.log_out_on_browser()
|
||||
console.debug("Deleting access token from config locally")
|
||||
hosting.delete_token_from_config()
|
||||
logout(loglevel) # type: ignore
|
||||
|
||||
|
||||
db_cli = typer.Typer()
|
||||
@ -489,126 +436,6 @@ def makemigrations(
|
||||
|
||||
@cli.command()
|
||||
def deploy(
|
||||
key: Optional[str] = typer.Option(
|
||||
None,
|
||||
"-k",
|
||||
"--deployment-key",
|
||||
help="The name of the deployment. Domain name safe characters only.",
|
||||
),
|
||||
app_name: str = typer.Option(
|
||||
config.app_name,
|
||||
"--app-name",
|
||||
help="The name of the App to deploy under.",
|
||||
hidden=True,
|
||||
),
|
||||
regions: List[str] = typer.Option(
|
||||
list(),
|
||||
"-r",
|
||||
"--region",
|
||||
help="The regions to deploy to.",
|
||||
),
|
||||
envs: List[str] = typer.Option(
|
||||
list(),
|
||||
"--env",
|
||||
help="The environment variables to set: <key>=<value>. For multiple envs, repeat this option, e.g. --env k1=v2 --env k2=v2.",
|
||||
),
|
||||
cpus: Optional[int] = typer.Option(
|
||||
None, help="The number of CPUs to allocate.", hidden=True
|
||||
),
|
||||
memory_mb: Optional[int] = typer.Option(
|
||||
None, help="The amount of memory to allocate.", hidden=True
|
||||
),
|
||||
auto_start: Optional[bool] = typer.Option(
|
||||
None,
|
||||
help="Whether to auto start the instance.",
|
||||
hidden=True,
|
||||
),
|
||||
auto_stop: Optional[bool] = typer.Option(
|
||||
None,
|
||||
help="Whether to auto stop the instance.",
|
||||
hidden=True,
|
||||
),
|
||||
frontend_hostname: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--frontend-hostname",
|
||||
help="The hostname of the frontend.",
|
||||
hidden=True,
|
||||
),
|
||||
interactive: bool = typer.Option(
|
||||
True,
|
||||
help="Whether to list configuration options and ask for confirmation.",
|
||||
),
|
||||
with_metrics: Optional[str] = typer.Option(
|
||||
None,
|
||||
help="Setting for metrics scraping for the deployment. Setup required in user code.",
|
||||
hidden=True,
|
||||
),
|
||||
with_tracing: Optional[str] = typer.Option(
|
||||
None,
|
||||
help="Setting to export tracing for the deployment. Setup required in user code.",
|
||||
hidden=True,
|
||||
),
|
||||
upload_db_file: bool = typer.Option(
|
||||
False,
|
||||
help="Whether to include local sqlite db files when uploading to hosting service.",
|
||||
hidden=True,
|
||||
),
|
||||
loglevel: constants.LogLevel = typer.Option(
|
||||
config.loglevel, help="The log level to use."
|
||||
),
|
||||
):
|
||||
"""Deploy the app to the Reflex hosting service."""
|
||||
from reflex_cli import cli as hosting_cli
|
||||
|
||||
from reflex.utils import export as export_utils
|
||||
from reflex.utils import prerequisites
|
||||
|
||||
# Set the log level.
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
# Only check requirements if interactive. There is user interaction for requirements update.
|
||||
if interactive:
|
||||
dependency.check_requirements()
|
||||
|
||||
# Check if we are set up.
|
||||
if prerequisites.needs_reinit(frontend=True):
|
||||
_init(name=config.app_name, loglevel=loglevel)
|
||||
prerequisites.check_latest_package_version(constants.ReflexHostingCLI.MODULE_NAME)
|
||||
|
||||
hosting_cli.deploy(
|
||||
app_name=app_name,
|
||||
export_fn=lambda zip_dest_dir,
|
||||
api_url,
|
||||
deploy_url,
|
||||
frontend,
|
||||
backend,
|
||||
zipping: export_utils.export(
|
||||
zip_dest_dir=zip_dest_dir,
|
||||
api_url=api_url,
|
||||
deploy_url=deploy_url,
|
||||
frontend=frontend,
|
||||
backend=backend,
|
||||
zipping=zipping,
|
||||
loglevel=loglevel.subprocess_level(),
|
||||
upload_db_file=upload_db_file,
|
||||
),
|
||||
key=key,
|
||||
regions=regions,
|
||||
envs=envs,
|
||||
cpus=cpus,
|
||||
memory_mb=memory_mb,
|
||||
auto_start=auto_start,
|
||||
auto_stop=auto_stop,
|
||||
frontend_hostname=frontend_hostname,
|
||||
interactive=interactive,
|
||||
with_metrics=with_metrics,
|
||||
with_tracing=with_tracing,
|
||||
loglevel=loglevel.subprocess_level(),
|
||||
)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def deployv2(
|
||||
app_name: str = typer.Option(
|
||||
config.app_name,
|
||||
"--app-name",
|
||||
@ -660,8 +487,8 @@ def deployv2(
|
||||
),
|
||||
):
|
||||
"""Deploy the app to the Reflex hosting service."""
|
||||
from reflex_cli.utils import dependency
|
||||
from reflex_cli.v2 import cli as hosting_cli
|
||||
from reflex_cli.v2.utils import dependency
|
||||
|
||||
from reflex.utils import export as export_utils
|
||||
from reflex.utils import prerequisites
|
||||
@ -671,6 +498,13 @@ def deployv2(
|
||||
# Set the log level.
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
if not token:
|
||||
# make sure user is logged in.
|
||||
if interactive:
|
||||
hosting_cli.login()
|
||||
else:
|
||||
raise SystemExit("Token is required for non-interactive mode.")
|
||||
|
||||
# Only check requirements if interactive.
|
||||
# There is user interaction for requirements update.
|
||||
if interactive:
|
||||
@ -703,7 +537,7 @@ def deployv2(
|
||||
envfile=envfile,
|
||||
hostname=hostname,
|
||||
interactive=interactive,
|
||||
loglevel=loglevel.subprocess_level(),
|
||||
loglevel=type(loglevel).INFO, # type: ignore
|
||||
token=token,
|
||||
project=project,
|
||||
)
|
||||
@ -711,15 +545,10 @@ def deployv2(
|
||||
|
||||
cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
|
||||
cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")
|
||||
cli.add_typer(
|
||||
deployments_cli,
|
||||
name="deployments",
|
||||
help="Subcommands for managing the Deployments.",
|
||||
)
|
||||
cli.add_typer(
|
||||
hosting_cli,
|
||||
name="apps",
|
||||
help="Subcommands for managing the Deployments.",
|
||||
name="cloud",
|
||||
help="Subcommands for managing the reflex cloud.",
|
||||
)
|
||||
cli.add_typer(
|
||||
custom_components_cli,
|
||||
|
@ -1288,6 +1288,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
||||
return
|
||||
|
||||
if name in self.backend_vars:
|
||||
# abort if unchanged
|
||||
if self._backend_vars.get(name) == value:
|
||||
return
|
||||
self._backend_vars.__setitem__(name, value)
|
||||
self.dirty_vars.add(name)
|
||||
self._mark_dirty()
|
||||
@ -3596,6 +3599,14 @@ class MutableProxy(wrapt.ObjectProxy):
|
||||
self._self_state = state
|
||||
self._self_field_name = field_name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Get the representation of the wrapped object.
|
||||
|
||||
Returns:
|
||||
The representation of the wrapped object.
|
||||
"""
|
||||
return f"{self.__class__.__name__}({self.__wrapped__})"
|
||||
|
||||
def _mark_dirty(
|
||||
self,
|
||||
wrapped=None,
|
||||
|
@ -1408,13 +1408,22 @@ def validate_and_create_app_using_remote_template(app_name, template, templates)
|
||||
"""
|
||||
# If user selects a template, it needs to exist.
|
||||
if template in templates:
|
||||
from reflex_cli.v2.utils import hosting
|
||||
|
||||
authenticated_token = hosting.authenticated_token()
|
||||
if not authenticated_token or not authenticated_token[0]:
|
||||
console.print(
|
||||
f"Please use `reflex login` to access the '{template}' template."
|
||||
)
|
||||
raise typer.Exit(3)
|
||||
|
||||
template_url = templates[template].code_url
|
||||
else:
|
||||
# Check if the template is a github repo.
|
||||
if template.startswith("https://github.com"):
|
||||
template_url = f"{template.strip('/').replace('.git', '')}/archive/main.zip"
|
||||
else:
|
||||
console.error(f"Template `{template}` not found.")
|
||||
console.error(f"Template `{template}` not found or invalid.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
if template_url is None:
|
||||
@ -1451,7 +1460,7 @@ def generate_template_using_ai(template: str | None = None) -> str:
|
||||
|
||||
|
||||
def fetch_remote_templates(
|
||||
template: Optional[str] = None,
|
||||
template: str,
|
||||
) -> tuple[str, dict[str, Template]]:
|
||||
"""Fetch the available remote templates.
|
||||
|
||||
@ -1460,9 +1469,6 @@ def fetch_remote_templates(
|
||||
|
||||
Returns:
|
||||
The selected template and the available templates.
|
||||
|
||||
Raises:
|
||||
Exit: If the template is not valid or if the template is not specified.
|
||||
"""
|
||||
available_templates = {}
|
||||
|
||||
@ -1474,19 +1480,7 @@ def fetch_remote_templates(
|
||||
console.debug(f"Error while fetching templates: {e}")
|
||||
template = constants.Templates.DEFAULT
|
||||
|
||||
if template == constants.Templates.DEFAULT:
|
||||
return template, available_templates
|
||||
|
||||
if template in available_templates:
|
||||
return template, available_templates
|
||||
|
||||
else:
|
||||
if template is not None:
|
||||
console.error(f"{template!r} is not a valid template name.")
|
||||
console.print(
|
||||
f"Go to the templates page ({constants.Templates.REFLEX_TEMPLATES_URL}) and copy the command to init with a template."
|
||||
)
|
||||
raise typer.Exit(0)
|
||||
return template, available_templates
|
||||
|
||||
|
||||
def initialize_app(
|
||||
@ -1501,6 +1495,9 @@ def initialize_app(
|
||||
|
||||
Returns:
|
||||
The name of the template.
|
||||
|
||||
Raises:
|
||||
Exit: If the template is not valid or unspecified.
|
||||
"""
|
||||
# Local imports to avoid circular imports.
|
||||
from reflex.utils import telemetry
|
||||
@ -1528,7 +1525,10 @@ def initialize_app(
|
||||
# change to the default to allow creation of default app
|
||||
template = constants.Templates.DEFAULT
|
||||
elif template == constants.Templates.CHOOSE_TEMPLATES:
|
||||
template, templates = fetch_remote_templates()
|
||||
console.print(
|
||||
f"Go to the templates page ({constants.Templates.REFLEX_TEMPLATES_URL}) and copy the command to init with a template."
|
||||
)
|
||||
raise typer.Exit(0)
|
||||
|
||||
# If the blank template is selected, create a blank app.
|
||||
if template in (constants.Templates.DEFAULT,):
|
||||
|
@ -129,7 +129,7 @@ def _prepare_event(event: str, **kwargs) -> dict:
|
||||
|
||||
cpuinfo = get_cpu_info()
|
||||
|
||||
additional_keys = ["template", "context", "detail"]
|
||||
additional_keys = ["template", "context", "detail", "user_uuid"]
|
||||
additional_fields = {
|
||||
key: value for key in additional_keys if (value := kwargs.get(key)) is not None
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import Callable, Coroutine, Generator, Type
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
@ -89,6 +90,11 @@ def DynamicRoute():
|
||||
@rx.page(route="/arg/[arg_str]")
|
||||
def arg() -> rx.Component:
|
||||
return rx.vstack(
|
||||
rx.input(
|
||||
value=DynamicState.router.session.client_token,
|
||||
read_only=True,
|
||||
id="token",
|
||||
),
|
||||
rx.data_list.root(
|
||||
rx.data_list.item(
|
||||
rx.data_list.label("rx.State.arg_str (dynamic)"),
|
||||
@ -172,6 +178,8 @@ def driver(dynamic_route: AppHarness) -> Generator[WebDriver, None, None]:
|
||||
"""
|
||||
assert dynamic_route.app_instance is not None, "app is not running"
|
||||
driver = dynamic_route.frontend()
|
||||
# TODO: drop after flakiness is resolved
|
||||
driver.implicitly_wait(30)
|
||||
try:
|
||||
yield driver
|
||||
finally:
|
||||
@ -373,17 +381,22 @@ async def test_on_load_navigate_non_dynamic(
|
||||
async def test_render_dynamic_arg(
|
||||
dynamic_route: AppHarness,
|
||||
driver: WebDriver,
|
||||
token: str,
|
||||
):
|
||||
"""Assert that dynamic arg var is rendered correctly in different contexts.
|
||||
|
||||
Args:
|
||||
dynamic_route: harness for DynamicRoute app.
|
||||
driver: WebDriver instance.
|
||||
token: The token visible in the driver browser.
|
||||
"""
|
||||
assert dynamic_route.app_instance is not None
|
||||
with poll_for_navigation(driver):
|
||||
driver.get(f"{dynamic_route.frontend_url}/arg/0")
|
||||
|
||||
# TODO: drop after flakiness is resolved
|
||||
time.sleep(3)
|
||||
|
||||
def assert_content(expected: str, expect_not: str):
|
||||
ids = [
|
||||
"state-arg_str",
|
||||
@ -398,7 +411,8 @@ async def test_render_dynamic_arg(
|
||||
el = driver.find_element(By.ID, id)
|
||||
assert el
|
||||
assert (
|
||||
dynamic_route.poll_for_content(el, exp_not_equal=expect_not) == expected
|
||||
dynamic_route.poll_for_content(el, timeout=30, exp_not_equal=expect_not)
|
||||
== expected
|
||||
)
|
||||
|
||||
assert_content("0", "")
|
||||
|
@ -19,10 +19,14 @@ def UploadFile():
|
||||
|
||||
import reflex as rx
|
||||
|
||||
LARGE_DATA = "DUMMY" * 1024 * 512
|
||||
|
||||
class UploadState(rx.State):
|
||||
_file_data: Dict[str, str] = {}
|
||||
event_order: List[str] = []
|
||||
progress_dicts: List[dict] = []
|
||||
disabled: bool = False
|
||||
large_data: str = ""
|
||||
|
||||
async def handle_upload(self, files: List[rx.UploadFile]):
|
||||
for file in files:
|
||||
@ -33,6 +37,7 @@ def UploadFile():
|
||||
for file in files:
|
||||
upload_data = await file.read()
|
||||
self._file_data[file.filename or ""] = upload_data.decode("utf-8")
|
||||
self.large_data = LARGE_DATA
|
||||
yield UploadState.chain_event
|
||||
|
||||
def upload_progress(self, progress):
|
||||
@ -41,13 +46,15 @@ def UploadFile():
|
||||
self.progress_dicts.append(progress)
|
||||
|
||||
def chain_event(self):
|
||||
assert self.large_data == LARGE_DATA
|
||||
self.large_data = ""
|
||||
self.event_order.append("chain_event")
|
||||
|
||||
def index():
|
||||
return rx.vstack(
|
||||
rx.input(
|
||||
value=UploadState.router.session.client_token,
|
||||
is_read_only=True,
|
||||
read_only=True,
|
||||
id="token",
|
||||
),
|
||||
rx.heading("Default Upload"),
|
||||
@ -56,6 +63,7 @@ def UploadFile():
|
||||
rx.button("Select File"),
|
||||
rx.text("Drag and drop files here or click to select files"),
|
||||
),
|
||||
disabled=UploadState.disabled,
|
||||
),
|
||||
rx.button(
|
||||
"Upload",
|
||||
|
Loading…
Reference in New Issue
Block a user