Compare commits

..

2 Commits

Author SHA1 Message Date
Lendemor
dbd074c33d add docs from reflex-web 2024-02-26 17:18:28 +01:00
Lendemor
334db18bfa move readme trads to free docs folder 2024-02-26 17:14:24 +01:00
1024 changed files with 110172 additions and 122843 deletions

View File

@ -1,17 +1,11 @@
[run]
source = reflex
branch = true
omit =
*/pyi_generator.py
reflex/__main__.py
reflex/app_module_for_backend.py
reflex/components/chakra/*
reflex/experimental/*
[report]
show_missing = true
# TODO bump back to 79
fail_under = 70
fail_under = 69
precision = 2
# Regexes for lines to exclude from consideration
@ -30,9 +24,6 @@ exclude_also =
# Don't complain about abstract methods, they aren't run:
@(abc\.)?abstractmethod
# Don't complain about overloaded methods:
@overload
ignore_errors = True

1
.github/CODEOWNERS vendored
View File

@ -1 +0,0 @@
@reflex-dev/reflex-team

View File

@ -2,6 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---

View File

@ -1,23 +0,0 @@
---
name: Custom Component Request
about: Suggest a new custom component for Reflex
title: ''
labels: 'custom component request'
assignees: ''
---
**Describe the Custom Component**
A clear and concise description of what the custom component does.
- What is the purpose of the custom component?
- What is the expected behavior of the custom component?
- What are the use cases for the custom component?
**Specifics (please complete the following information):**
- Do you have a specific react package in mind? (Optional):
**Additional context**
Add any other context about the custom component here.

View File

@ -1,19 +0,0 @@
---
name: Enhancement Request
about: Suggest an enhancement for an existing Reflex feature.
title: ''
labels: 'enhancement'
assignees: ''
---
**Describe the Enhancement you want**
A clear and concise description of what the improvement does.
- Which feature do you want to improve? (and what problem does it have)
- What is the benefit of the enhancement?
- Show an example/usecase were the improvement are needed.
**Additional context**
Add any other context here.

View File

@ -1,18 +0,0 @@
---
name: Feature Request
about: Suggest a new feature for Reflex
title: ''
labels: 'feature request'
assignees: ''
---
**Describe the Features**
A clear and concise description of what the features does.
- What is the purpose of the feature?
- Show an example / use cases for the new feature.
**Additional context**
Add any other context here.

View File

@ -6,7 +6,7 @@
#
# Exit conditions:
# - Python of version `python-version` is ready to be invoked as `python`.
# - Poetry of version `poetry-version` is ready to be invoked as `poetry`.
# - Poetry of version `poetry-version` is ready ot be invoked as `poetry`.
# - If `run-poetry-install` is true, deps as defined in `pyproject.toml` will have been installed into the venv at `create-venv-at-path`.
name: 'Setup Reflex build environment'
@ -18,7 +18,7 @@ inputs:
poetry-version:
description: 'Poetry version to install'
required: false
default: '1.8.3'
default: '1.3.1'
run-poetry-install:
description: 'Whether to run poetry install on current dir'
required: false
@ -106,8 +106,3 @@ runs:
run: |
source ${{ inputs.create-venv-at-path }}/*/activate
poetry install --only-root --no-interaction
- name: Install uv
shell: bash
run: |
poetry run pip install uv

View File

@ -1,2 +0,0 @@
paths-ignore:
- "**/tests/**"

View File

@ -1,11 +1,14 @@
name: benchmarking
on:
pull_request:
types:
- closed
push:
branches: [main]
paths-ignore:
- "**/*.md"
- '**/*.md'
pull_request:
branches: [main]
paths-ignore:
- '**/*.md'
permissions:
contents: read
@ -15,21 +18,21 @@ defaults:
shell: bash
env:
PYTHONIOENCODING: "utf8"
PYTHONIOENCODING: 'utf8'
TELEMETRY_ENABLED: false
NODE_OPTIONS: "--max_old_space_size=8192"
PR_TITLE: ${{ github.event.pull_request.title }}
NODE_OPTIONS: '--max_old_space_size=4096'
jobs:
reflex-web:
# if: github.event.pull_request.merged == true
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
strategy:
fail-fast: false
matrix:
# Show OS combos first in GUI
os: [ubuntu-latest]
python-version: ["3.12.8"]
node-version: ["18.x"]
python-version: ['3.11.4']
node-version: ['16.x']
runs-on: ${{ matrix.os }}
steps:
@ -48,12 +51,12 @@ jobs:
uses: actions/checkout@v4
with:
repository: reflex-dev/reflex-web
ref: main
ref: reflex-ci
path: reflex-web
- name: Install Requirements for reflex-web
working-directory: ./reflex-web
run: poetry run uv pip install -r requirements.txt
run: poetry run pip install -r requirements.txt
- name: Init Website for reflex-web
working-directory: ./reflex-web
run: poetry run reflex init
@ -61,83 +64,14 @@ jobs:
run: |
# Check that npm is home
npm -v
poetry run bash benchmarks/lighthouse.sh ./reflex-web prod
poetry run bash scripts/benchmarks.sh ./reflex-web prod
env:
LHCI_GITHUB_APP_TOKEN: $
- name: Run Benchmarks
# Only run if the database creds are available in this context.
run: poetry run python benchmarks/benchmark_lighthouse.py "$GITHUB_SHA" ./integration/benchmarks/.lighthouseci
if: ${{ env.DATABASE_URL }}
working-directory: ./integration/benchmarks
run: poetry run python benchmarks.py "$GITHUB_SHA" .lighthouseci
env:
GITHUB_SHA: ${{ github.sha }}
reflex-dist-size: # This job is used to calculate the size of the Reflex distribution (wheel file)
if: github.event.pull_request.merged == true
timeout-minutes: 30
strategy:
# Prioritize getting more information out of the workflow (even if something fails)
fail-fast: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
python-version: 3.12.8
run-poetry-install: true
create-venv-at-path: .venv
- name: Build reflex
run: |
poetry build
- name: Upload benchmark results
# Only run if the database creds are available in this context.
run:
poetry run python benchmarks/benchmark_package_size.py --os ubuntu-latest
--python-version 3.12.8 --commit-sha "${{ github.sha }}" --pr-id "${{ github.event.pull_request.id }}"
--branch-name "${{ github.head_ref || github.ref_name }}"
--path ./dist
reflex-venv-size: # This job calculates the total size of Reflex and its dependencies
if: github.event.pull_request.merged == true
timeout-minutes: 30
strategy:
# Prioritize getting more information out of the workflow (even if something fails)
fail-fast: false
matrix:
# Show OS combos first in GUI
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.12.8"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set up python
id: setup-python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: 1.3.1
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: .venv
- name: Run poetry install
shell: bash
run: |
python -m venv .venv
source .venv/*/activate
poetry install --without dev --no-interaction --no-root
- name: Install uv
shell: bash
run: |
poetry run pip install uv
- name: calculate and upload size
run:
poetry run python benchmarks/benchmark_package_size.py --os "${{ matrix.os }}"
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
--pr-id "${{ github.event.pull_request.id }}"
--branch-name "${{ github.head_ref || github.ref_name }}"
--path ./.venv
PR_TITLE: ${{ github.event.pull_request.title }}

View File

@ -1,21 +1,17 @@
name: check-generated-pyi
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
cancel-in-progress: true
on:
push:
branches: ["main"]
# We don't just trigger on make_pyi.py and the components dir, because
branches: [ "main" ]
# We don't just trigger on pyi_generator.py and the components dir, because
# there are other things that can change the generator output
# e.g. black version, reflex.Component, reflex.Var.
paths-ignore:
- "**/*.md"
- '**/*.md'
pull_request:
branches: ["main"]
branches: [ "main" ]
paths-ignore:
- "**/*.md"
- '**/*.md'
jobs:
check-generated-pyi-components:
@ -25,15 +21,15 @@ jobs:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
python-version: "3.12.8"
python-version: "3.11.5"
run-poetry-install: true
create-venv-at-path: .venv
- run: |
poetry run python scripts/make_pyi.py
poetry run python scripts/pyi_generator.py
if [[ $(git status --porcelain) ]]; then
git status
git diff
echo "ERROR: make_pyi.py output is out of date. Please run scripts/make_pyi.py and commit the changes."
echo "ERROR: pyi_generator.py output is out of date. Please run scripts/pyi_generator.py and commit the changes."
exit 1
else
echo "No diffs - AOK!"

View File

@ -1,40 +0,0 @@
name: integration-node-latest
on:
push:
branches:
- main
pull_request:
branches:
- main
env:
TELEMETRY_ENABLED: false
REFLEX_USE_SYSTEM_NODE: true
jobs:
check_latest_node:
runs-on: ubuntu-22.04
strategy:
matrix:
python-version: ["3.12.8"]
split_index: [1, 2]
node-version: ["node"]
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
python-version: ${{ matrix.python-version }}
run-poetry-install: true
create-venv-at-path: .venv
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: |
poetry run uv pip install pyvirtualdisplay pillow pytest-split
poetry run playwright install --with-deps
- run: |
poetry run pytest tests/test_node_version.py
poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}}

View File

@ -1,86 +0,0 @@
name: check-outdated-dependencies
on:
push: # This will trigger the action when a pull request is opened or updated.
branches:
- "release/**" # This will trigger the action when any branch starting with "release/" is created.
workflow_dispatch: # Allow manual triggering if needed.
jobs:
backend:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- uses: ./.github/actions/setup_build_env
with:
python-version: '3.10'
run-poetry-install: true
create-venv-at-path: .venv
- name: Check outdated backend dependencies
run: |
outdated=$(poetry show -oT)
echo "Outdated:"
echo "$outdated"
filtered_outdated=$(echo "$outdated" | grep -vE 'pyright|ruff' || true)
if [ ! -z "$filtered_outdated" ]; then
echo "Outdated dependencies found:"
echo "$filtered_outdated"
exit 1
else
echo "All dependencies are up to date. (pyright and ruff are ignored)"
fi
frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
python-version: "3.10.16"
run-poetry-install: true
create-venv-at-path: .venv
- name: Clone Reflex Website Repo
uses: actions/checkout@v4
with:
repository: reflex-dev/reflex-web
ref: main
path: reflex-web
- name: Install Requirements for reflex-web
working-directory: ./reflex-web
run: poetry run uv pip install $(grep -ivE "reflex " requirements.txt)
- name: Install additional dependencies for DB access
run: poetry run uv pip install psycopg
- name: Init Website for reflex-web
working-directory: ./reflex-web
run: poetry run reflex init
- name: Run Website and Check for errors
run: |
poetry run bash scripts/integration.sh ./reflex-web dev
- name: Check outdated frontend dependencies
working-directory: ./reflex-web/.web
run: |
raw_outdated=$(/home/runner/.local/share/reflex/bun/bin/bun outdated)
outdated=$(echo "$raw_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\|' || true)
echo "Outdated:"
echo "$outdated"
# Ignore 3rd party dependencies that are not updated.
filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images|ag-grid' || true)
no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
if [ ! -z "$no_extra" ]; then
echo "Outdated dependencies found:"
echo "$filtered_outdated"
exit 1
else
echo "All dependencies are up to date. (3rd party packages are ignored)"
fi

View File

@ -1,103 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
schedule:
- cron: "36 7 * * 4"
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: javascript-typescript
build-mode: none
- language: python
build-mode: none
- language: actions
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
config-file: .github/codeql-config.yml
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

@ -1,17 +0,0 @@
name: 'Dependency Review'
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v4
- name: 'Dependency Review'
uses: actions/dependency-review-action@v4
with:
allow-licenses: Apache-2.0, BSD-2-Clause, BSD-3-Clause, HPND, ISC, MIT, MPL-2.0, Unlicense, Python-2.0, Python-2.0.1, Apache-2.0 AND MIT, BSD-2-Clause AND BSD-3-Clause, Apache-2.0 AND BSD-3-Clause
allow-dependencies-licenses: 'pkg:pypi/lazy-loader'

View File

@ -1,18 +1,14 @@
name: integration-app-harness
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
cancel-in-progress: true
on:
push:
branches: ["main"]
branches: [ "main" ]
paths-ignore:
- "**/*.md"
- '**/*.md'
pull_request:
branches: ["main"]
branches: [ "main" ]
paths-ignore:
- "**/*.md"
- '**/*.md'
permissions:
contents: read
@ -22,11 +18,9 @@ jobs:
timeout-minutes: 30
strategy:
matrix:
state_manager: ["redis", "memory"]
python-version: ["3.11.11", "3.12.8", "3.13.1"]
split_index: [1, 2]
fail-fast: false
runs-on: ubuntu-22.04
state_manager: [ "redis", "memory" ]
python-version: ["3.11.5", "3.12.0"]
runs-on: ubuntu-latest
services:
# Label used to access the service container
redis:
@ -41,16 +35,22 @@ jobs:
# Maps port 6379 on service container to the host
- 6379:6379
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
python-version: ${{ matrix.python-version }}
run-poetry-install: true
create-venv-at-path: .venv
- run: poetry run uv pip install pyvirtualdisplay pillow pytest-split pytest-retry
- name: Run app harness tests
env:
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
run: |
poetry run playwright install chromium
poetry run pytest tests/integration --retries 3 --maxfail=5 --splits 2 --group ${{matrix.split_index}}
- run: poetry run pip install pyvirtualdisplay pillow
- name: Run app harness tests
env:
SCREENSHOT_DIR: /tmp/screenshots
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
run: |
poetry run pytest integration
- uses: actions/upload-artifact@v4
name: Upload failed test screenshots
if: always()
with:
name: failed_test_screenshots
path: /tmp/screenshots

View File

@ -2,17 +2,13 @@ name: integration-tests
on:
push:
branches: ["main"]
branches: ['main']
paths-ignore:
- "**/*.md"
- '**/*.md'
pull_request:
branches: ["main"]
branches: ['main']
paths-ignore:
- "**/*.md"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
cancel-in-progress: true
- '**/*.md'
permissions:
contents: read
@ -27,53 +23,59 @@ env:
# TODO: can we fix windows encoding natively within reflex? Bug above can hit real users too (less common, but possible)
# - Catch encoding errors when printing logs
# - Best effort print lines that contain illegal chars (map to some default char, etc.)
PYTHONIOENCODING: "utf8"
PYTHONIOENCODING: 'utf8'
TELEMETRY_ENABLED: false
NODE_OPTIONS: "--max_old_space_size=8192"
PR_TITLE: ${{ github.event.pull_request.title }}
NODE_OPTIONS: '--max_old_space_size=4096'
jobs:
example-counter-and-nba-proxy:
env:
OUTPUT_FILE: import_benchmark.json
example-counter:
timeout-minutes: 30
strategy:
# Prioritize getting more information out of the workflow (even if something fails)
fail-fast: false
matrix:
# Show OS combos first in GUI
os: [ubuntu-latest, windows-latest]
python-version: ['3.10.16', '3.11.11', '3.12.8', '3.13.1']
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: ['16.x']
python-version: ['3.8.18', '3.9.18', '3.10.13', '3.11.5', '3.12.0']
exclude:
- os: windows-latest
python-version: "3.11.11"
python-version: '3.10.13'
- os: windows-latest
python-version: '3.10.16'
python-version: '3.9.18'
- os: windows-latest
python-version: '3.8.18'
include:
- os: windows-latest
python-version: "3.11.9"
- os: windows-latest
python-version: '3.10.11'
- os: windows-latest
python-version: '3.9.13'
- os: windows-latest
python-version: '3.8.10'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- uses: ./.github/actions/setup_build_env
with:
python-version: ${{ matrix.python-version }}
run-poetry-install: true
create-venv-at-path: .venv
- name: Clone Reflex Examples Repo
uses: actions/checkout@v4
with:
repository: reflex-dev/reflex-examples
path: reflex-examples
- name: Install requirements for counter example
working-directory: ./reflex-examples/counter
run: |
poetry run uv pip install -r requirements.txt
- name: Install additional dependencies for DB access
run: poetry run uv pip install psycopg
poetry run pip install -r requirements.txt
- name: Check export --backend-only before init for counter example
working-directory: ./reflex-examples/counter
run: |
@ -94,40 +96,25 @@ jobs:
# Check that npm is home
npm -v
poetry run bash scripts/integration.sh ./reflex-examples/counter dev
- name: Install requirements for nba proxy example
working-directory: ./reflex-examples/nba-proxy
run: |
poetry run uv pip install -r requirements.txt
- name: Install additional dependencies for DB access
run: poetry run uv pip install psycopg
- name: Check export --backend-only before init for nba-proxy example
working-directory: ./reflex-examples/nba-proxy
run: |
poetry run reflex export --backend-only
- name: Init Website for nba-proxy example
working-directory: ./reflex-examples/nba-proxy
run: |
poetry run reflex init --loglevel debug
- name: Run Website and Check for errors
run: |
# Check that npm is home
npm -v
poetry run bash scripts/integration.sh ./reflex-examples/nba-proxy dev
reflex-web:
strategy:
fail-fast: false
matrix:
# Show OS combos first in GUI
os: [ubuntu-latest]
python-version: ["3.11.11", "3.12.8"]
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.10.10', '3.11.4']
node-version: ['16.x']
env:
REFLEX_WEB_WINDOWS_OVERRIDE: "1"
REFLEX_WEB_WINDOWS_OVERRIDE: '1'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- uses: ./.github/actions/setup_build_env
with:
python-version: ${{ matrix.python-version }}
@ -143,72 +130,7 @@ jobs:
- name: Install Requirements for reflex-web
working-directory: ./reflex-web
run: poetry run uv pip install $(grep -ivE "reflex " requirements.txt)
- name: Install additional dependencies for DB access
run: poetry run uv pip install psycopg
- name: Init Website for reflex-web
working-directory: ./reflex-web
run: poetry run reflex init
- name: Run Website and Check for errors
run: |
# Check that npm is home
npm -v
poetry run bash scripts/integration.sh ./reflex-web prod
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.11"
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:
fail-fast: false
matrix:
# Note: py311 version chosen due to available arm64 darwin builds.
python-version: ["3.11.9", "3.12.8"]
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
python-version: ${{ matrix.python-version }}
run-poetry-install: true
create-venv-at-path: .venv
- name: Clone Reflex Website Repo
uses: actions/checkout@v4
with:
repository: reflex-dev/reflex-web
ref: main
path: reflex-web
- name: Install Requirements for reflex-web
working-directory: ./reflex-web
run: poetry run uv pip install -r requirements.txt
- name: Install additional dependencies for DB access
run: poetry run uv pip install psycopg
run: poetry run pip install -r requirements.txt
- name: Init Website for reflex-web
working-directory: ./reflex-web
run: poetry run reflex init

View File

@ -1,9 +1,5 @@
name: integration-tests-wsl
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
cancel-in-progress: true
on:
push:
branches: ['main']
@ -37,8 +33,6 @@ jobs:
path: reflex-examples
- uses: Vampire/setup-wsl@v3
with:
distribution: Ubuntu-24.04
- name: Install Python
shell: wsl-bash {0}
@ -58,42 +52,32 @@ jobs:
run: |
poetry install
- name: Install uv
shell: wsl-bash {0}
run: |
poetry run pip install uv
- name: Install requirements for counter example
working-directory: ./reflex-examples/counter
shell: wsl-bash {0}
run: |
poetry run uv pip install -r requirements.txt
poetry run pip install -r requirements.txt
- name: Check export --backend-only before init for counter example
working-directory: ./reflex-examples/counter
shell: wsl-bash {0}
run: |
export TELEMETRY_ENABLED=false
poetry run reflex export --backend-only
- name: Check run --backend-only before init for counter example
shell: wsl-bash {0}
run: |
export TELEMETRY_ENABLED=false
dos2unix scripts/integration.sh
poetry run bash scripts/integration.sh ./reflex-examples/counter dev 8001 --backend-only --backend-port 8001
- name: Init Website for counter example
working-directory: ./reflex-examples/counter
shell: wsl-bash {0}
run: |
export TELEMETRY_ENABLED=false
poetry run reflex init --loglevel debug
- name: Check export for counter example
working-directory: ./reflex-examples/counter
shell: wsl-bash {0}
run: |
export TELEMETRY_ENABLED=false
poetry run reflex export --frontend-only --loglevel debug
- name: Run Website and Check for errors
shell: wsl-bash {0}
run: |
export TELEMETRY_ENABLED=false
poetry run bash scripts/integration.sh ./reflex-examples/counter dev

View File

@ -1,34 +0,0 @@
name: performance-tests
on:
push:
branches:
- "main" # or "master"
paths-ignore:
- "**/*.md"
pull_request:
workflow_dispatch:
env:
TELEMETRY_ENABLED: false
NODE_OPTIONS: "--max_old_space_size=8192"
PR_TITLE: ${{ github.event.pull_request.title }}
APP_HARNESS_HEADLESS: 1
PYTHONUNBUFFERED: 1
jobs:
benchmarks:
name: Run benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
python-version: 3.12.8
run-poetry-install: true
create-venv-at-path: .venv
- name: Run benchmarks
uses: CodSpeedHQ/action@v3
with:
token: ${{ secrets.CODSPEED_TOKEN }}
run: poetry run pytest tests/benchmarks --codspeed

View File

@ -1,17 +1,13 @@
name: pre-commit
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
cancel-in-progress: true
on:
pull_request:
branches: ["main"]
branches: ['main']
push:
# Note even though this job is called "pre-commit" and runs "pre-commit", this job will run
# also POST-commit on main also! In case there are mishandled merge conflicts / bad auto-resolves
# when merging into main branch.
branches: ["main"]
branches: ['main']
jobs:
pre-commit:
@ -23,12 +19,12 @@ jobs:
with:
# running vs. one version of Python is OK
# i.e. ruff, black, etc.
python-version: 3.12.8
python-version: 3.11.5
run-poetry-install: true
create-venv-at-path: .venv
# TODO pre-commit related stuff can be cached too (not a bottleneck yet)
- run: |
poetry run uv pip install pre-commit
poetry run pip install pre-commit
poetry run pre-commit run --all-files
env:
SKIP: update-pyi-files

View File

@ -1,16 +1,12 @@
name: reflex-init-in-docker-test
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
cancel-in-progress: true
on:
push:
branches: ['main']
branches: [ "main" ]
paths-ignore:
- '**/*.md'
pull_request:
branches: ['main']
branches: [ "main" ]
paths-ignore:
- '**/*.md'
@ -21,12 +17,12 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
- shell: bash
run: |
# Run reflex init in a docker container
# cwd is repo root
docker build -f tests/integration/init-test/Dockerfile -t reflex-init-test tests/integration/init-test
docker run --rm -v $(pwd):/reflex-repo/ reflex-init-test /reflex-repo/tests/integration/init-test/in_docker_test_script.sh
- shell: bash
run: |
# Run reflex init in a docker container
# cwd is repo root
docker build -f integration/init-test/Dockerfile -t reflex-init-test integration/init-test
docker run --rm -v $(pwd):/reflex-repo/ reflex-init-test /reflex-repo/integration/init-test/in_docker_test_script.sh

View File

@ -1,25 +1,21 @@
name: unit-tests
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
cancel-in-progress: true
on:
push:
branches: ["main"]
branches: [ "main" ]
paths-ignore:
- "**/*.md"
- '**/*.md'
pull_request:
branches: ["main"]
branches: [ "main" ]
paths-ignore:
- "**/*.md"
- '**/*.md'
permissions:
contents: read
defaults:
run:
shell: bash
run:
shell: bash
jobs:
unit-tests:
@ -27,21 +23,24 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.10.16", "3.11.11", "3.12.8", "3.13.1"]
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.8.18", "3.9.18", "3.10.13", "3.11.5", "3.12.0"]
# Windows is a bit behind on Python version availability in Github
exclude:
- os: windows-latest
python-version: "3.11.11"
python-version: "3.10.13"
- os: windows-latest
python-version: "3.10.16"
python-version: "3.9.18"
- os: windows-latest
python-version: "3.8.18"
include:
- os: windows-latest
python-version: "3.11.9"
- os: windows-latest
python-version: "3.10.11"
- os: windows-latest
python-version: "3.9.13"
- os: windows-latest
python-version: "3.8.10"
runs-on: ${{ matrix.os }}
# Service containers to run with `runner-job`
services:
# Label used to access the service container
@ -57,53 +56,20 @@ jobs:
# Maps port 6379 on service container to the host
- 6379:6379
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
python-version: ${{ matrix.python-version }}
run-poetry-install: true
create-venv-at-path: .venv
- name: Run unit tests
run: |
export PYTHONUNBUFFERED=1
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
- name: Run unit tests w/ redis
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
export PYTHONUNBUFFERED=1
export REDIS_URL=redis://localhost:6379
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
# Change to explicitly install v1 when reflex-hosting-cli is compatible with v2
- name: Run unit tests w/ pydantic v1
run: |
export PYTHONUNBUFFERED=1
poetry run uv pip install "pydantic~=1.10"
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
- name: Generate coverage report
run: poetry run coverage html
unit-tests-macos:
timeout-minutes: 30
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
strategy:
fail-fast: false
matrix:
# Note: py310, py311 versions chosen due to available arm64 darwin builds.
python-version: ["3.10.11", "3.11.9", "3.12.8", "3.13.1"]
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
python-version: ${{ matrix.python-version }}
run-poetry-install: true
create-venv-at-path: .venv
- name: Run unit tests
run: |
export PYTHONUNBUFFERED=1
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
- name: Run unit tests w/ pydantic v1
run: |
export PYTHONUNBUFFERED=1
poetry run uv pip install "pydantic~=1.10"
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
- name: Run unit tests
run: |
export PYTHONUNBUFFERED=1
poetry run pytest tests --cov --no-cov-on-fail --cov-report=
- name: Run unit tests w/ redis
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
export PYTHONUNBUFFERED=1
export REDIS_URL=redis://localhost:6379
poetry run pytest tests --cov --no-cov-on-fail --cov-report=
- run: poetry run coverage html

5
.gitignore vendored
View File

@ -1,10 +1,7 @@
**/.DS_Store
**/*.pyc
assets/external/*
dist/*
examples/
.web
.states
.idea
.vscode
.coverage
@ -14,5 +11,3 @@ venv
requirements.txt
.pyi_generator_last_run
.pyi_generator_diff
reflex.db
.codspeed

View File

@ -1,38 +1,21 @@
fail_fast: true
repos:
- repo: https://github.com/psf/black
rev: 22.10.0
hooks:
- id: black
args: [integration, reflex, tests]
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.9.6
rev: v0.0.244
hooks:
- id: ruff-format
args: [reflex, tests]
- id: ruff
args: ["--fix", "--exit-non-zero-on-fix"]
exclude: '^integration/benchmarks/'
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
args: ["reflex"]
# Run pyi check before pyright because pyright can fail if pyi files are wrong.
- repo: local
hooks:
- id: update-pyi-files
name: update-pyi-files
always_run: true
language: system
require_serial: true
description: 'Update pyi files as needed'
entry: python3 scripts/make_pyi.py
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.393
rev: v1.1.313
hooks:
- id: pyright
args: [reflex, tests]
args: [integration, reflex, tests]
language: system
- repo: https://github.com/terrencepreilly/darglint
@ -41,3 +24,11 @@ repos:
- id: darglint
exclude: '^reflex/reflex.py'
- repo: local
hooks:
- id: update-pyi-files
name: update-pyi-files
always_run: true
language: system
description: 'Update pyi files as needed'
entry: python scripts/pyi_generator.py

View File

@ -5,7 +5,7 @@
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socioeconomic status,
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

View File

@ -8,7 +8,7 @@ Here is a quick guide on how to run Reflex repo locally so you can start contrib
**Prerequisites:**
- Python >= 3.10
- Python >= 3.8
- Poetry version >= 1.4.0 and add it to your path (see [Poetry Docs](https://python-poetry.org/docs/#installation) for more info).
**1. Fork this repository:**
@ -69,7 +69,7 @@ In your `reflex` directory run make sure all the unit tests are still passing us
This will fail if code coverage is below 70%.
``` bash
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
poetry run pytest tests --cov --no-cov-on-fail --cov-report=
```
Next make sure all the following tests pass. This ensures that every new change has proper documentation and type checking.
@ -80,14 +80,14 @@ poetry run pyright reflex tests
find reflex tests -name "*.py" -not -path reflex/reflex.py | xargs poetry run darglint
```
Finally, run `ruff` to format your code.
Finally, run `black` to format your code.
``` bash
poetry run ruff format .
poetry run black reflex tests
```
Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and `make_pyi` will run automatically before each commit.
Note that pre-commit will only be installed when you use a Python version >= 3.10.
Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and Black will run automatically before each commit.
Note that pre-commit will only be installed when you use a Python version >= 3.8.
``` bash
pre-commit install
@ -96,15 +96,6 @@ pre-commit install
That's it you can now submit your PR. Thanks for contributing to Reflex!
## Editing Templates
To edit the templates in Reflex you can do so in two way.
Change to the basic `blank` template can be done in the `reflex/.templates/apps/blank` directory.
Others templates can be edited in their own repository. For example the `sidebar` template can be found in the [`reflex-sidebar`](https://github.com/reflex-dev/sidebar-template) repository.
## Other Notes
For some pull requests when adding new components you will have to generate a pyi file for the new component. This is done by running the following command in the `reflex` directory.
@ -112,5 +103,5 @@ For some pull requests when adding new components you will have to generate a py
(Please check in with the team before adding a new component to Reflex we are cautious about adding new components to Reflex's core.)
``` bash
poetry run python scripts/make_pyi.py
poetry run python scripts/pyi_generator.py
```

View File

@ -3,13 +3,14 @@
```
<div align="center">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/readme/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/readme/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
<hr>
### **✨ Performant, customizable web apps in pure Python. Deploy in seconds. ✨**
[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex)
![tests](https://github.com/pynecone-io/pynecone/actions/workflows/integration.yml/badge.svg)
![versions](https://img.shields.io/pypi/pyversions/reflex.svg)
[![Documentation](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction)
[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ)
@ -17,24 +18,12 @@
---
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md)
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/readme/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/readme/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/readme/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/readme/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/readme/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/readme/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/readme/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/readme/kr/README.md)
---
# Reflex
Reflex is a library to build full-stack web apps in pure Python.
Key features:
* **Pure Python** - Write your app's frontend and backend all in Python, no need to learn Javascript.
* **Full Flexibility** - Reflex is easy to get started with, but can also scale to complex apps.
* **Deploy Instantly** - After building, deploy your app with a [single command](https://reflex.dev/docs/hosting/deploy-quick-start/) or host it on your own server.
See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) to learn how Reflex works under the hood.
## ⚙️ Installation
Open a terminal and run (Requires Python 3.10+):
Open a terminal and run (Requires Python 3.8+):
```bash
pip install reflex
@ -72,15 +61,13 @@ Let's go over an example: creating an image generation UI around [DALL·E](https
&nbsp;
<div align="center">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="A frontend wrapper for DALL·E, shown in the process of generating an image." width="550" />
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/readme/images/dalle.gif" alt="A frontend wrapper for DALL·E, shown in the process of generating an image." width="550" />
</div>
&nbsp;
Here is the complete code to create this. This is all done in one Python file!
```python
import reflex as rx
import openai
@ -109,7 +96,6 @@ class State(rx.State):
self.image_url = response.data[0].url
self.processing, self.complete = False, True
def index():
return rx.center(
rx.vstack(
@ -119,15 +105,14 @@ def index():
on_blur=State.set_prompt,
width="25em",
),
rx.button(
"Generate Image",
on_click=State.get_image,
width="25em",
loading=State.processing
),
rx.button("Generate Image", on_click=State.get_image, width="25em"),
rx.cond(
State.complete,
rx.image(src=State.image_url, width="20em"),
State.processing,
rx.chakra.circular_progress(is_indeterminate=True),
rx.cond(
State.complete,
rx.image(src=State.image_url, width="20em"),
),
),
align="center",
),
@ -135,22 +120,14 @@ def index():
height="100vh",
)
# Add state and page to the app.
app = rx.App()
app.add_page(index, title="Reflex:DALL-E")
```
## Let's break this down.
<div align="center">
<img src="docs/images/dalle_colored_code_example.png" alt="Explaining the differences between backend and frontend parts of the DALL-E app." width="900" />
</div>
### **Reflex UI**
Let's start with the UI.
@ -185,7 +162,7 @@ class State(rx.State):
The state defines all the variables (called vars) in an app that can change and the functions that change them.
Here the state is comprised of a `prompt` and `image_url`. There are also the booleans `processing` and `complete` to indicate when to disable the button (during image generation) and when to show the resulting image.
Here the state is comprised of a `prompt` and `image_url`. There are also the booleans `processing` and `complete` to indicate when to show the circular progress and image.
### **Event Handlers**
@ -228,16 +205,24 @@ You can create a multi-page app by adding more pages.
<div align="center">
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Component Library](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Templates](https://reflex.dev/templates/) &nbsp; | &nbsp; 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start) &nbsp;
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Component Library](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Gallery](https://reflex.dev/docs/gallery) &nbsp; | &nbsp; 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy) &nbsp;
</div>
## ✅ Status
Reflex launched in December 2022 with the name Pynecone.
As of February 2024, our hosting service is in alpha! During this time anyone can deploy their apps for free. See our [roadmap](https://github.com/reflex-dev/reflex/issues/2727) to see what's planned.
As of July 2023, we are in the **Public Beta** stage.
- :white_check_mark: **Public Alpha**: Anyone can install and use Reflex. There may be issues, but we are working to resolve them actively.
- :large_orange_diamond: **Public Beta**: Stable enough for non-enterprise use-cases.
- **Public Hosting Beta**: _Optionally_, deploy and host your apps on Reflex!
- **Public**: Reflex is production ready.
Reflex has new releases and features coming every week! Make sure to :star: star and :eyes: watch this repository to stay up to date.
@ -249,7 +234,7 @@ We welcome contributions of any size! Below are some good ways to get started in
- **GitHub Discussions**: A great way to talk about features you want added or things that are confusing/need clarification.
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) are an excellent way to report bugs. Additionally, you can try and solve an existing issue and submit a PR.
We are actively looking for contributors, no matter your skill level or experience. To contribute check out [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
We are actively looking for contributors, no matter your skill level or experience. To contribute check out [CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
## All Thanks To Our Contributors:

View File

@ -1,3 +0,0 @@
"""Reflex benchmarks."""
WINDOWS_SKIP_REASON = "Takes too much time as a result of npm"

View File

@ -1,147 +0,0 @@
"""Extracts the compile times from the JSON files in the specified directory and inserts them into the database."""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
from utils import send_data_to_posthog
def extract_stats_from_json(json_file: str) -> list[dict]:
"""Extracts the stats from the JSON data and returns them as a list of dictionaries.
Args:
json_file: The JSON file to extract the stats data from.
Returns:
list[dict]: The stats for each test.
"""
with Path(json_file).open() as file:
json_data = json.load(file)
# Load the JSON data if it is a string, otherwise assume it's already a dictionary
data = json.loads(json_data) if isinstance(json_data, str) else json_data
# Initialize an empty list to store the stats for each test
test_stats = []
# Iterate over each test in the 'benchmarks' list
for test in data.get("benchmarks", []):
group = test.get("group", None)
stats = test.get("stats", {})
full_name = test.get("fullname")
file_name = (
full_name.split("/")[-1].split("::")[0].strip(".py") if full_name else None
)
test_name = test.get("name", "Unknown Test")
test_stats.append(
{
"test_name": test_name,
"group": group,
"stats": stats,
"full_name": full_name,
"file_name": file_name,
}
)
return test_stats
def insert_benchmarking_data(
os_type_version: str,
python_version: str,
performance_data: list[dict],
commit_sha: str,
pr_title: str,
branch_name: str,
event_type: str,
pr_id: str,
):
"""Insert the benchmarking data into the database.
Args:
os_type_version: The OS type and version to insert.
python_version: The Python version to insert.
performance_data: The performance data of reflex web to insert.
commit_sha: The commit SHA to insert.
pr_title: The PR title to insert.
branch_name: The name of the branch.
event_type: Type of github event(push, pull request, etc).
pr_id: Id of the PR.
"""
# Prepare the event data
properties = {
"os": os_type_version,
"python_version": python_version,
"distinct_id": commit_sha,
"pr_title": pr_title,
"branch_name": branch_name,
"event_type": event_type,
"performance": performance_data,
"pr_id": pr_id,
}
send_data_to_posthog("simple_app_benchmark", properties)
def main():
"""Runs the benchmarks and inserts the results."""
# Get the commit SHA and JSON directory from the command line arguments
parser = argparse.ArgumentParser(description="Run benchmarks and process results.")
parser.add_argument(
"--os", help="The OS type and version to insert into the database."
)
parser.add_argument(
"--python-version", help="The Python version to insert into the database."
)
parser.add_argument(
"--commit-sha", help="The commit SHA to insert into the database."
)
parser.add_argument(
"--benchmark-json",
help="The JSON file containing the benchmark results.",
)
parser.add_argument(
"--pr-title",
help="The PR title to insert into the database.",
)
parser.add_argument(
"--branch-name",
help="The current branch",
required=True,
)
parser.add_argument(
"--event-type",
help="The github event type",
required=True,
)
parser.add_argument(
"--pr-id",
help="ID of the PR.",
required=True,
)
args = parser.parse_args()
# Get the PR title from env or the args. For the PR merge or push event, there is no PR title, leaving it empty.
pr_title = args.pr_title or os.getenv("PR_TITLE", "")
# Get the results of pytest benchmarks
cleaned_benchmark_results = extract_stats_from_json(args.benchmark_json)
# Insert the data into the database
insert_benchmarking_data(
os_type_version=args.os,
python_version=args.python_version,
performance_data=cleaned_benchmark_results,
commit_sha=args.commit_sha,
pr_title=pr_title,
branch_name=args.branch_name,
event_type=args.event_type,
pr_id=args.pr_id,
)
if __name__ == "__main__":
main()

View File

@ -1,128 +0,0 @@
"""Extract and upload benchmarking data to PostHog."""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
from utils import send_data_to_posthog
def extract_stats_from_json(json_file: str) -> dict:
"""Extracts the stats from the JSON data and returns them as dictionaries.
Args:
json_file: The JSON file to extract the stats data from.
Returns:
dict: The stats for each test.
"""
with Path(json_file).open() as file:
json_data = json.load(file)
# Load the JSON data if it is a string, otherwise assume it's already a dictionary
data = json.loads(json_data) if isinstance(json_data, str) else json_data
result = data.get("results", [{}])[0]
return {
k: v
for k, v in result.items()
if k in ("mean", "stddev", "median", "min", "max")
}
def insert_benchmarking_data(
os_type_version: str,
python_version: str,
performance_data: dict,
commit_sha: str,
pr_title: str,
branch_name: str,
pr_id: str,
app_name: str,
):
"""Insert the benchmarking data into the database.
Args:
os_type_version: The OS type and version to insert.
python_version: The Python version to insert.
performance_data: The imports performance data to insert.
commit_sha: The commit SHA to insert.
pr_title: The PR title to insert.
branch_name: The name of the branch.
pr_id: Id of the PR.
app_name: The name of the app being measured.
"""
properties = {
"os": os_type_version,
"python_version": python_version,
"distinct_id": commit_sha,
"pr_title": pr_title,
"branch_name": branch_name,
"pr_id": pr_id,
"performance": performance_data,
"app_name": app_name,
}
send_data_to_posthog("import_benchmark", properties)
def main():
"""Runs the benchmarks and inserts the results."""
# Get the commit SHA and JSON directory from the command line arguments
parser = argparse.ArgumentParser(description="Run benchmarks and process results.")
parser.add_argument(
"--os", help="The OS type and version to insert into the database."
)
parser.add_argument(
"--python-version", help="The Python version to insert into the database."
)
parser.add_argument(
"--commit-sha", help="The commit SHA to insert into the database."
)
parser.add_argument(
"--benchmark-json",
help="The JSON file containing the benchmark results.",
)
parser.add_argument(
"--pr-title",
help="The PR title to insert into the database.",
)
parser.add_argument(
"--branch-name",
help="The current branch",
required=True,
)
parser.add_argument(
"--app-name",
help="The name of the app measured.",
required=True,
)
parser.add_argument(
"--pr-id",
help="ID of the PR.",
required=True,
)
args = parser.parse_args()
# Get the PR title from env or the args. For the PR merge or push event, there is no PR title, leaving it empty.
pr_title = args.pr_title or os.getenv("PR_TITLE", "")
cleaned_benchmark_results = extract_stats_from_json(args.benchmark_json)
# Insert the data into the database
insert_benchmarking_data(
os_type_version=args.os,
python_version=args.python_version,
performance_data=cleaned_benchmark_results,
commit_sha=args.commit_sha,
pr_title=pr_title,
branch_name=args.branch_name,
app_name=args.app_name,
pr_id=args.pr_id,
)
if __name__ == "__main__":
main()

View File

@ -1,75 +0,0 @@
"""Extracts the Lighthouse scores from the JSON files in the specified directory and inserts them into the database."""
from __future__ import annotations
import json
import sys
from pathlib import Path
from utils import send_data_to_posthog
def insert_benchmarking_data(
lighthouse_data: dict,
commit_sha: str,
):
"""Insert the benchmarking data into the database.
Args:
lighthouse_data: The Lighthouse data to insert.
commit_sha: The commit SHA to insert.
"""
properties = {
"distinct_id": commit_sha,
"lighthouse_data": lighthouse_data,
}
# Send the data to PostHog
send_data_to_posthog("lighthouse_benchmark", properties)
def get_lighthouse_scores(directory_path: str | Path) -> dict:
"""Extracts the Lighthouse scores from the JSON files in the specified directory.
Args:
directory_path (str): The path to the directory containing the JSON files.
Returns:
dict: The Lighthouse scores.
"""
scores = {}
directory_path = Path(directory_path)
try:
for filename in directory_path.iterdir():
if filename.suffix == ".json" and filename.stem != "manifest":
data = json.loads(filename.read_text())
# Extract scores and add them to the dictionary with the filename as key
scores[data["finalUrl"].replace("http://localhost:3000/", "/")] = {
"performance_score": data["categories"]["performance"]["score"],
"accessibility_score": data["categories"]["accessibility"]["score"],
"best_practices_score": data["categories"]["best-practices"][
"score"
],
"seo_score": data["categories"]["seo"]["score"],
}
except Exception as e:
return {"error": e}
return scores
def main():
"""Runs the benchmarks and inserts the results into the database."""
# Get the commit SHA and JSON directory from the command line arguments
commit_sha = sys.argv[1]
json_dir = sys.argv[2]
# Get the Lighthouse scores
lighthouse_scores = get_lighthouse_scores(json_dir)
# Insert the data into the database
insert_benchmarking_data(lighthouse_scores, commit_sha)
if __name__ == "__main__":
main()

View File

@ -1,135 +0,0 @@
"""Checks the size of a specific directory and uploads result to Posthog."""
import argparse
import os
from pathlib import Path
from utils import get_directory_size, get_python_version, send_data_to_posthog
def get_package_size(venv_path: Path, os_name):
"""Get the size of a specified package.
Args:
venv_path: The path to the venv.
os_name: Name of os.
Returns:
The total size of the package in bytes.
Raises:
ValueError: when venv does not exist or python version is None.
"""
python_version = get_python_version(venv_path, os_name)
print("Python version:", python_version)
if python_version is None:
raise ValueError("Error: Failed to determine Python version.")
is_windows = "windows" in os_name
package_dir: Path = (
venv_path / "lib" / f"python{python_version}" / "site-packages"
if not is_windows
else venv_path / "Lib" / "site-packages"
)
if not package_dir.exists():
raise ValueError(
"Error: Virtual environment does not exist or is not activated."
)
total_size = get_directory_size(package_dir)
return total_size
def insert_benchmarking_data(
os_type_version: str,
python_version: str,
commit_sha: str,
pr_title: str,
branch_name: str,
pr_id: str,
path: str,
):
"""Insert the benchmarking data into PostHog.
Args:
os_type_version: The OS type and version to insert.
python_version: The Python version to insert.
commit_sha: The commit SHA to insert.
pr_title: The PR title to insert.
branch_name: The name of the branch.
pr_id: The id of the PR.
path: The path to the dir or file to check size.
"""
if "./dist" in path:
size = get_directory_size(Path(path))
else:
size = get_package_size(Path(path), os_type_version)
# Prepare the event data
properties = {
"path": path,
"os": os_type_version,
"python_version": python_version,
"distinct_id": commit_sha,
"pr_title": pr_title,
"branch_name": branch_name,
"pr_id": pr_id,
"size_mb": round(
size / (1024 * 1024), 3
), # save size in MB and round to 3 places
}
send_data_to_posthog("package_size", properties)
def main():
"""Runs the benchmarks and inserts the results."""
parser = argparse.ArgumentParser(description="Run benchmarks and process results.")
parser.add_argument(
"--os", help="The OS type and version to insert into the database."
)
parser.add_argument(
"--python-version", help="The Python version to insert into the database."
)
parser.add_argument(
"--commit-sha", help="The commit SHA to insert into the database."
)
parser.add_argument(
"--pr-title",
help="The PR title to insert into the database.",
)
parser.add_argument(
"--branch-name",
help="The current branch",
required=True,
)
parser.add_argument(
"--pr-id",
help="The pr id",
required=True,
)
parser.add_argument(
"--path",
help="The path to the vnenv.",
required=True,
)
args = parser.parse_args()
# Get the PR title from env or the args. For the PR merge or push event, there is no PR title, leaving it empty.
pr_title = args.pr_title or os.getenv("PR_TITLE", "")
# Insert the data into the database
insert_benchmarking_data(
os_type_version=args.os,
python_version=args.python_version,
commit_sha=args.commit_sha,
pr_title=pr_title,
branch_name=args.branch_name,
pr_id=args.pr_id,
path=args.path,
)
if __name__ == "__main__":
main()

View File

@ -1,106 +0,0 @@
"""Checks the size of a specific directory and uploads result to Posthog."""
import argparse
import os
from pathlib import Path
from utils import get_directory_size, send_data_to_posthog
def insert_benchmarking_data(
os_type_version: str,
python_version: str,
app_name: str,
commit_sha: str,
pr_title: str,
branch_name: str,
pr_id: str,
path: str,
):
"""Insert the benchmarking data into PostHog.
Args:
app_name: The name of the app being measured.
os_type_version: The OS type and version to insert.
python_version: The Python version to insert.
commit_sha: The commit SHA to insert.
pr_title: The PR title to insert.
branch_name: The name of the branch.
pr_id: The id of the PR.
path: The path to the dir or file to check size.
"""
size = get_directory_size(Path(path))
# Prepare the event data
properties = {
"app_name": app_name,
"os": os_type_version,
"python_version": python_version,
"distinct_id": commit_sha,
"pr_title": pr_title,
"branch_name": branch_name,
"pr_id": pr_id,
"size_mb": round(
size / (1024 * 1024), 3
), # save size in MB and round to 3 places
}
send_data_to_posthog("web-size", properties)
def main():
"""Runs the benchmarks and inserts the results."""
parser = argparse.ArgumentParser(description="Run benchmarks and process results.")
parser.add_argument(
"--os", help="The OS type and version to insert into the database."
)
parser.add_argument(
"--python-version", help="The Python version to insert into the database."
)
parser.add_argument(
"--commit-sha", help="The commit SHA to insert into the database."
)
parser.add_argument(
"--pr-title",
help="The PR title to insert into the database.",
)
parser.add_argument(
"--branch-name",
help="The current branch",
required=True,
)
parser.add_argument(
"--app-name",
help="The name of the app measured.",
required=True,
)
parser.add_argument(
"--pr-id",
help="The pr id",
required=True,
)
parser.add_argument(
"--path",
help="The current path to app to check.",
required=True,
)
args = parser.parse_args()
# Get the PR title from env or the args. For the PR merge or push event, there is no PR title, leaving it empty.
pr_title = args.pr_title or os.getenv("PR_TITLE", "")
# Insert the data into the database
insert_benchmarking_data(
app_name=args.app_name,
os_type_version=args.os,
python_version=args.python_version,
commit_sha=args.commit_sha,
pr_title=pr_title,
branch_name=args.branch_name,
pr_id=args.pr_id,
path=args.path,
)
if __name__ == "__main__":
main()

View File

@ -1,20 +0,0 @@
"""Shared conftest for all benchmark tests."""
import pytest
from reflex.testing import AppHarness, AppHarnessProd
@pytest.fixture(
scope="session", params=[AppHarness, AppHarnessProd], ids=["dev", "prod"]
)
def app_harness_env(request):
"""Parametrize the AppHarness class to use for the test, either dev or prod.
Args:
request: The pytest fixture request object.
Returns:
The AppHarness class to use for the test.
"""
return request.param

View File

@ -1,74 +0,0 @@
"""Utility functions for the benchmarks."""
import os
import subprocess
from pathlib import Path
import httpx
from httpx import HTTPError
def get_python_version(venv_path: Path, os_name):
"""Get the python version of python in a virtual env.
Args:
venv_path: Path to virtual environment.
os_name: Name of os.
Returns:
The python version.
"""
python_executable = (
venv_path / "bin" / "python"
if "windows" not in os_name
else venv_path / "Scripts" / "python.exe"
)
try:
output = subprocess.check_output(
[str(python_executable), "--version"], stderr=subprocess.STDOUT
)
python_version = output.decode("utf-8").strip().split()[1]
return ".".join(python_version.split(".")[:-1])
except subprocess.CalledProcessError:
return None
def get_directory_size(directory: Path):
"""Get the size of a directory in bytes.
Args:
directory: The directory to check.
Returns:
The size of the dir in bytes.
"""
total_size = 0
for dirpath, _, filenames in os.walk(directory):
for f in filenames:
fp = Path(dirpath) / f
total_size += fp.stat().st_size
return total_size
def send_data_to_posthog(event, properties):
"""Send data to PostHog.
Args:
event: The event to send.
properties: The properties to send.
Raises:
HTTPError: When there is an error sending data to PostHog.
"""
event_data = {
"api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb",
"event": event,
"properties": properties,
}
with httpx.Client() as client:
response = client.post("https://app.posthog.com/capture/", json=event_data)
if response.status_code != 200:
raise HTTPError(
f"Error sending data to PostHog: {response.status_code} - {response.text}"
)

View File

@ -0,0 +1,2 @@
.web
__pycache__/*

View File

@ -2,7 +2,7 @@
encode gzip
@backend_routes path /_event/* /ping /_upload /_upload/*
@backend_routes path /_event/* /_upload /ping
handle @backend_routes {
reverse_proxy app:8000
}

39
docker-example/Dockerfile Normal file
View File

@ -0,0 +1,39 @@
# Stage 1: init
FROM python:3.11 as init
# Pass `--build-arg API_URL=http://app.example.com:8000` during build
ARG API_URL
# Copy local context to `/app` inside container (see .dockerignore)
WORKDIR /app
COPY . .
# Create virtualenv which will be copied into final container
ENV VIRTUAL_ENV=/app/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
RUN python3.11 -m venv $VIRTUAL_ENV
# Install app requirements and reflex inside virtualenv
RUN pip install -r requirements.txt
# Deploy templates and prepare app
RUN reflex init
# Export static copy of frontend to /app/.web/_static
RUN reflex export --frontend-only --no-zip
# Copy static files out of /app to save space in backend image
RUN mv .web/_static /tmp/_static
RUN rm -rf .web && mkdir .web
RUN mv /tmp/_static .web/_static
# Stage 2: copy artifacts into slim image
FROM python:3.11-slim
ARG API_URL
WORKDIR /app
RUN adduser --disabled-password --home /app reflex
COPY --chown=reflex --from=init /app /app
USER reflex
ENV PATH="/app/.venv/bin:$PATH" API_URL=$API_URL
CMD reflex db migrate && reflex run --env prod --backend-only

View File

@ -1,30 +1,66 @@
# Reflex Docker Examples
# Reflex Docker Container
This directory contains several examples of how to deploy Reflex apps using docker.
This example describes how to create and use a container image for Reflex with your own code.
In all cases, ensure that your `requirements.txt` file is up to date and
includes the `reflex` package.
## Update Requirements
## `simple-two-port`
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.
The most basic production deployment exposes two HTTP ports and relies on an
existing load balancer to forward the traffic appropriately.
## Build Reflex Container Image
## `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 . --build-arg API_URL=http://app.example.com:8000
```
## `production-compose`
Ensure that `API_URL` is set to the publicly accessible hostname or IP where the app
will be hosted.
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.
## Start Container Service
## `production-app-platform`
Finally, you can start your Reflex container service as follows:
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.
```bash
docker run -p 3000:3000 -p 8000:8000 --name app reflex-app:latest
```
It may take a few seconds for the service to become available.
# 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 `compose.yaml`, `Caddy.Dockerfile` and `Caddyfile` to your project directory. The production
build leverages the same `Dockerfile` described above.
## Customize `Caddyfile`
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)
```bash
DOMAIN=example.com docker compose build
```
This will build both the `app` service from the existing `Dockerfile` and the `webserver`
service via `Caddy.Dockerfile` that copies the `Caddyfile` and static frontend export
from the `app` service into the container.
## 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.

View File

@ -0,0 +1,21 @@
# During build and run, set environment DOMAIN pointing
# to publicly accessible domain where app will be hosted
services:
app:
image: local/reflex-app
build:
context: .
args:
API_URL: https://${DOMAIN:-localhost}
webserver:
environment:
DOMAIN: ${DOMAIN:-localhost}
ports:
- 443:443
- 80:80 # for acme-challenge via HTTP
build:
context: .
dockerfile: Caddy.Dockerfile
depends_on:
- app

View File

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

View File

@ -1,65 +0,0 @@
# 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.13 as init
ARG uv=/root/.local/bin/uv
# Install `uv` for faster package bootstrapping
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.13-slim
WORKDIR /app
RUN adduser --disabled-password --home /app reflex
COPY --chown=reflex --from=init /app /app
# Install libpq-dev for psycopg (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}

View File

@ -1,113 +0,0 @@
# 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

View File

@ -1,8 +0,0 @@
.web
.git
__pycache__/*
Dockerfile
Caddy.Dockerfile
compose.yaml
compose.*.yaml
uploaded_files

View File

@ -1,52 +0,0 @@
# This docker file is intended to be used with docker compose to deploy a production
# instance of a Reflex app.
# Stage 1: init
FROM python:3.13 as init
ARG uv=/root/.local/bin/uv
# Install `uv` for faster package bootstrapping
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
# Export static copy of frontend to /app/.web/_static
RUN reflex export --frontend-only --no-zip
# Copy static files out of /app to save space in backend image
RUN mv .web/_static /tmp/_static
RUN rm -rf .web && mkdir .web
RUN mv /tmp/_static .web/_static
# Stage 2: copy artifacts into slim image
FROM python:3.13-slim
WORKDIR /app
RUN adduser --disabled-password --home /app reflex
COPY --chown=reflex --from=init /app /app
# Install libpq-dev for psycopg (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

View File

@ -1,75 +0,0 @@
# 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
```

View File

@ -1,25 +0,0 @@
# Use this override file to run the app in prod mode with postgres and redis
# docker compose -f compose.yaml -f compose.prod.yaml up -d
services:
db:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: secret
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis
restart: always
app:
environment:
DB_URL: postgresql+psycopg://postgres:secret@db/postgres
REDIS_URL: redis://redis:6379
depends_on:
- db
- redis
volumes:
postgres-data:

View File

@ -1,18 +0,0 @@
# Use this override file with `compose.prod.yaml` to run admin tools
# for production services.
# docker compose -f compose.yaml -f compose.prod.yaml -f compose.tools.yaml up -d
services:
adminer:
image: adminer
ports:
- 8080:8080
redis-commander:
image: ghcr.io/joeferner/redis-commander:latest
environment:
- REDIS_HOSTS=local:redis:6379
ports:
- "8081:8081"
volumes:
redis-ui-settings:

View File

@ -1,41 +0,0 @@
# Base compose file production deployment of reflex app with Caddy webserver
# providing TLS termination and reverse proxying.
#
# See `compose.prod.yaml` for more robust and performant deployment option.
#
# During build and run, set environment DOMAIN pointing
# to publicly accessible domain where app will be hosted
services:
app:
image: local/reflex-app
environment:
DB_URL: sqlite:///data/reflex.db
build:
context: .
volumes:
- db-data:/app/data
- upload-data:/app/uploaded_files
restart: always
webserver:
environment:
DOMAIN: ${DOMAIN:-localhost}
ports:
- 443:443
- 80:80 # For acme-challenge via HTTP.
build:
context: .
dockerfile: Caddy.Dockerfile
volumes:
- caddy-data:/root/.caddy
restart: always
depends_on:
- app
volumes:
# SQLite data
db-data:
# Uploaded files
upload-data:
# TLS keys and certificates
caddy-data:

View File

@ -1,3 +0,0 @@
.web
!.web/bun.lockb
!.web/package.json

View File

@ -1,14 +0,0 @@
:{$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
}

View File

@ -1,62 +0,0 @@
# 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

View File

@ -1,37 +0,0 @@
# 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.

View File

@ -0,0 +1 @@
reflex

View File

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

View File

@ -1,14 +0,0 @@
:{$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
}

View File

@ -1,41 +0,0 @@
# This Dockerfile is used to deploy a single-container Reflex app instance
# to services like Render, Railway, Heroku, GCP, and others.
# 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
# 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
ENV PORT=$PORT API_URL=${API_URL:-http://localhost:$PORT} REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
# 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
# Copy local context to `/app` inside container (see .dockerignore)
COPY . .
# Install app requirements and reflex in the container
RUN pip install -r requirements.txt
# Deploy templates and prepare app
RUN reflex init
# Download all npm dependencies and compile frontend
RUN reflex export --frontend-only --no-zip && mv .web/_static/* /srv/ && rm -rf .web
# 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

View File

@ -1,36 +0,0 @@
# 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.

View File

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

View File

@ -1,26 +0,0 @@
# This Dockerfile is used to deploy a simple single-container Reflex app instance.
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
# Copy local context to `/app` inside container (see .dockerignore)
WORKDIR /app
COPY . .
# Install app requirements and reflex in the container
RUN pip install -r requirements.txt
# Deploy templates and prepare app
RUN reflex init
# Download all npm dependencies and compile frontend
RUN reflex export --frontend-only --no-zip
# Needed until Reflex properly passes SIGTERM on backend.
STOPSIGNAL SIGKILL
# Always apply migrations before starting the backend.
CMD [ -d alembic ] && reflex db migrate; \
redis-server --daemonize yes && \
exec reflex run --env prod

View File

@ -1,44 +0,0 @@
# 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
```

View File

@ -0,0 +1,214 @@
```python exec
import asyncio
from typing import Any
import reflex as rx
from pcweb.pages.docs import wrapping_react
from pcweb.pages.docs import library
```
# Browser Javascript
Reflex compiles your frontend code, defined as python functions, into a Javascript web application
that runs in the user's browser. There are instances where you may need to supply custom javascript
code to interop with Web APIs, use certain third-party libraries, or wrap low-level functionality
that is not exposed via Reflex's Python API.
```md alert
# Avoid Custom Javascript
Custom Javascript code in your Reflex app presents a maintenance challenge, as it will be harder to debug and may be unstable across Reflex versions.
Prefer to use the Python API whenever possible and file an issue if you need additional functionality that is not currently provided.
```
## Executing Script
There are four ways to execute custom Javascript code into your Reflex app:
* `rx.script` - Injects the script via `next/script` for efficient loading of inline and external Javascript code. Described further in the [component library]({library.other.script.path}).
* These components can be directly included in the body of a page, or they may
be passed to `rx.App(head_components=[rx.script(...)])` to be included in
the `<Head>` tag of all pages.
* `rx.call_script` - An event handler that evaluates arbitrary Javascript code,
and optionally returns the result to another event handler.
These previous two methods can work in tandem to load external scripts and then
call functions defined within them in response to user events.
The following two methods are geared towards wrapping components and are
described with examples in the [Wrapping React]({wrapping_react.overview.path})
section.
* `_get_hooks` and `_get_custom_code` in an `rx.Component` subclass
* `Var.create` with `_var_is_local=False`
## Inline Scripts
The `rx.script` component is the recommended way to load inline Javascript for greater control over
frontend behavior.
The functions and variables in the script can be accessed from backend event
handlers or frontend event triggers via the `rx.call_script` interface.
```python demo exec
class SoundEffectState(rx.State):
@rx.background
async def delayed_play(self):
await asyncio.sleep(1)
return rx.call_script("playFromStart(button_sfx)")
def sound_effect_demo():
return rx.hstack(
rx.script("""
var button_sfx = new Audio("/vintage-button-sound-effect.mp3")
function playFromStart (sfx) {sfx.load(); sfx.play()}"""),
rx.button("Play Immediately", on_click=rx.call_script("playFromStart(button_sfx)")),
rx.button("Play Later", on_click=SoundEffectState.delayed_play),
)
```
## External Scripts
External scripts can be loaded either from the `assets` directory, or from CDN URL, and then controlled
via `rx.call_script`.
```python demo
rx.vstack(
rx.script(
src="https://cdn.jsdelivr.net/gh/scottschiller/snowstorm@snowstorm_20131208/snowstorm-min.js",
on_ready=rx.call_script("snowStorm.autoStart = false; snowStorm.snowColor = '#111'"),
),
rx.button("Start Duststorm", on_click=rx.call_script("snowStorm.start()")),
rx.button("Toggle Duststorm", on_click=rx.call_script("snowStorm.toggleSnow()")),
)
```
## Accessing Client Side Values
The `rx.call_script` function accepts a `callback` parameter that expects an
Event Handler with one argument which will receive the result of evaluating the
Javascript code. This can be used to access client-side values such as the
`window.location` or current scroll location, or any previously defined value.
```python demo exec
class WindowState(rx.State):
location: dict[str, str] = {}
scroll_position: dict[str, int] = {}
def update_location(self, location):
self.location = location
def update_scroll_position(self, scroll_position):
self.scroll_position = {
"x": scroll_position[0],
"y": scroll_position[1],
}
def get_client_values(self):
return [
rx.call_script(
"window.location",
callback=WindowState.update_location
),
rx.call_script(
"[window.scrollX, window.scrollY]",
callback=WindowState.update_scroll_position,
),
]
def window_state_demo():
return rx.vstack(
rx.button("Update Values", on_click=WindowState.get_client_values),
rx.text(f"Scroll Position: {WindowState.scroll_position.to_string()}"),
rx.text("window.location:"),
rx.text_area(value=WindowState.location.to_string(), is_read_only=True),
on_mount=WindowState.get_client_values,
)
```
```md alert
# Allowed Callback Values
The `callback` parameter may be an `EventHandler` with one argument, or a lambda with one argument that returns an `EventHandler`.
If the callback is None, then no event is triggered.
```
## Using React Hooks
To use React Hooks directly in a Reflex app, you must subclass `rx.Component`,
typically `rx.Fragment` is used when the hook functionality has no visual
element. The hook code is returned by the `_get_hooks` method, which is expected
to return a `str` containing Javascript code which will be inserted into the
page component (i.e the render function itself).
For supporting code that must be defined outside of the component render
function, use `_get_custom_code`.
The following example uses `useEffect` to register global hotkeys on the
`document` object, and then triggers an event when a specific key is pressed.
```python demo exec
class GlobalKeyState(rx.State):
key: str = ""
def update_key(self, key):
self.key = key
class GlobalKeyWatcher(rx.Fragment):
# List of keys to trigger on
keys: rx.vars.Var[list[str]] = []
def _get_imports(self) -> rx.utils.imports.ImportDict:
return rx.utils.imports.merge_imports(
super()._get_imports(),
{
"react": {rx.utils.imports.ImportVar(tag="useEffect")}
},
)
def _get_hooks(self) -> str | None:
return """
useEffect(() => {
const handle_key = (_e0) => {
if (%s.includes(_e0.key))
%s
}
document.addEventListener("keydown", handle_key, false);
return () => {
document.removeEventListener("keydown", handle_key, false);
}
})
""" % (
self.keys,
rx.utils.format.format_event_chain(self.event_triggers["on_key_down"]),
)
def get_event_triggers(self) -> dict[str, Any]:
return {
"on_key_down": lambda e0: [e0.key],
}
def render(self):
return "" # No visual element, hooks only
def global_key_demo():
return rx.vstack(
GlobalKeyWatcher.create(
keys=["a", "s", "d", "w"],
on_key_down=GlobalKeyState.update_key,
),
rx.text("Press a, s, d or w to trigger an event"),
rx.heading(f"Last watched key pressed: {GlobalKeyState.key}"),
)
```
```md alert
# rx.utils.format.format_event_chain?
The `format_event_chain` function is used to format an event trigger defined on the component via `get_event_triggers` into a Javascript expression that can be used to actually trigger the event.
The Javascript code should do minimal work, preferring to hand off execution to a user-supplied python `EventHandler` for processing on the backend.
```

View File

@ -0,0 +1,191 @@
# Browser Storage
## rx.Cookie
Represents a state Var that is stored as a cookie in the browser. Currently only supports string values.
Parameters
- `name` : The name of the cookie on the client side.
- `path`: The cookie path. Use `/` to make the cookie accessible on all pages.
- `max_age` : Relative max age of the cookie in seconds from when the client receives it.
- `domain`: Domain for the cookie (e.g., `sub.domain.com` or `.allsubdomains.com`).
- `secure`: If the cookie is only accessible through HTTPS.
- `same_site`: Whether the cookie is sent with third-party requests. Can be one of (`True`, `False`, `None`, `lax`, `strict`).
```python
class CookieState(rx.State):
c1: str = rx.Cookie()
c2: str = rx.Cookie('c2 default')
# cookies with custom settings
c3: str = rx.Cookie(max_age=2) # expires after 2 second
c4: str = rx.Cookie(same_site='strict')
c5: str = rx.Cookie(path='/foo/') # only accessible on `/foo/`
c6: str = rx.Cookie(name='c6-custom-name')
```
## rx.remove_cookies
Remove a cookie from the client's browser.
Parameters:
- `key`: The name of cookie to remove.
```python
rx.button(
'Remove cookie', on_click=rx.remove_cookie('key')
)
```
This event can also be returned from an event handler:
```python
class CookieState(rx.State):
...
def logout(self):
return rx.remove_cookie('auth_token')
```
## rx.LocalStorage
Represents a state Var that is stored in localStorage in the browser. Currently only supports string values.
Parameters
- `name`: The name of the storage key on the client side.
```python
class LocalStorageState(rx.State):
# local storage with default settings
l1: str = rx.LocalStorage()
# local storage with custom settings
l2: str = rx.LocalStorage("l2 default")
l3: str = rx.LocalStorage(name="l3")
```
## rx.remove_local_storage
Remove a local storage item from the client's browser.
Parameters
- `key`: The key to remove from local storage.
```python
rx.button(
'Remove Local Storage',
on_click=rx.remove_local_storage('key'),
)
```
This event can also be returned from an event handler:
```python
class LocalStorageState(rx.State):
...
def logout(self):
return rx.remove_local_storage('local_storage_state.l1')
```
## rx.clear_local_storage()
Clear all local storage items from the client's browser. This may affect other
apps running in the same domain or libraries within your app that use local
storage.
```python
rx.button(
'Clear all Local Storage',
on_click=rx.clear_local_storage(),
)
```
# Serialization Strategies
If a non-trivial data structure should be stored in a `Cookie` or `LocalStorage` var it needs to
be serialized before and after storing it. It is recommended to use `rx.Base` for the data
which provides simple serialization helpers and works recursively in complex object structures.
```python demo exec
import reflex as rx
class AppSettings(rx.Base):
theme: str = 'light'
sidebar_visible: bool = True
update_frequency: int = 60
error_messages: list[str] = []
class ComplexLocalStorageState(rx.State):
data_raw: str = rx.LocalStorage("{}")
data: AppSettings = AppSettings()
settings_open: bool = False
def save_settings(self):
self.data_raw = self.data.json()
self.settings_open = False
def open_settings(self):
self.data = AppSettings.parse_raw(self.data_raw)
self.settings_open = True
def set_field(self, field, value):
setattr(self.data, field, value)
def app_settings():
return rx.chakra.form(
rx.foreach(
ComplexLocalStorageState.data.error_messages,
rx.text,
),
rx.chakra.form_label(
"Theme",
rx.chakra.input(
value=ComplexLocalStorageState.data.theme,
on_change=lambda v: ComplexLocalStorageState.set_field("theme", v),
),
),
rx.chakra.form_label(
"Sidebar Visible",
rx.chakra.switch(
is_checked=ComplexLocalStorageState.data.sidebar_visible,
on_change=lambda v: ComplexLocalStorageState.set_field(
"sidebar_visible",
v,
),
),
),
rx.chakra.form_label(
"Update Frequency (seconds)",
rx.chakra.number_input(
value=ComplexLocalStorageState.data.update_frequency,
on_change=lambda v: ComplexLocalStorageState.set_field(
"update_frequency",
v,
),
),
),
rx.button("Save", type="submit"),
on_submit=lambda _: ComplexLocalStorageState.save_settings(),
)
def app_settings_example():
return rx.fragment(
rx.chakra.modal(
rx.chakra.modal_overlay(
rx.chakra.modal_content(
rx.chakra.modal_header("App Settings"),
rx.chakra.modal_body(app_settings()),
),
),
is_open=ComplexLocalStorageState.settings_open,
on_close=ComplexLocalStorageState.set_settings_open(False),
),
rx.button("App Settings", on_click=ComplexLocalStorageState.open_settings),
)
```

90
docs/api-reference/cli.md Normal file
View File

@ -0,0 +1,90 @@
# CLI
The `reflex` command line interface (CLI) is a tool for creating and managing Reflex apps.
To see a list of all available commands, run `reflex --help`.
```bash
$ reflex --help
Usage: reflex [OPTIONS] COMMAND [ARGS]...
Reflex CLI to create, run, and deploy apps.
Options:
-v, --version Get the Reflex version.
--help Show this message and exit.
Commands:
db Subcommands for managing the database schema.
demo Run the demo app.
deploy Deploy the app to the Reflex hosting service.
deployments Subcommands for managing the Deployments.
export Export the app to a zip file.
init Initialize a new Reflex app in the current directory.
login Authenticate with Reflex hosting service.
logout Log out of access to Reflex hosting service.
run Run the app in the current directory.
```
## Init
The `reflex init` command creates a new Reflex app in the current directory.
If an `rxconfig.py` file already exists already, it will re-initialize the app with the latest template.
```bash
$ reflex init --help
Usage: reflex init [OPTIONS]
Initialize a new Reflex app in the current directory.
Options:
--name APP_NAME The name of the app to initialize.
--template [demo|sidebar|blank]
The template to initialize the app with.
--loglevel [debug|info|warning|error|critical]
The log level to use. [default:
LogLevel.INFO]
--help Show this message and exit.
```
## Run
The `reflex run` command runs the app in the current directory.
By default it runs your app in development mode.
This means that the app will automatically reload when you make changes to the code.
You can also run in production mode which will create an optimized build of your app.
You can configure the mode, as well as other options through flags.
```bash
$ reflex run --help
Usage: reflex run [OPTIONS]
Run the app in the current directory.
Options:
--env [dev|prod] The environment to run the app in.
[default: Env.DEV]
--frontend-only Execute only frontend.
--backend-only Execute only backend.
--frontend-port TEXT Specify a different frontend port.
[default: 3000]
--backend-port TEXT Specify a different backend port. [default:
8000]
--backend-host TEXT Specify the backend host. [default:
0.0.0.0]
--loglevel [debug|info|warning|error|critical]
The log level to use. [default:
LogLevel.INFO]
--help Show this message and exit.
```
## Export
You can export your app's frontend and backend to zip files using the `reflex export` command.
The frontend is a compiled NextJS app, which can be deployed to a static hosting service like Github Pages or Vercel.
However this is just a static build, so you will need to deploy the backend separately.
See the self-hosting guide for more information.

View File

@ -0,0 +1,263 @@
```python exec
from datetime import datetime
import reflex as rx
from pcweb.templates.docpage import docdemo, h1_comp, text_comp, docpage
SYNTHETIC_EVENTS = [
{
"name": "on_focus",
"description": "The on_focus event handler is called when the element (or some element inside of it) receives focus. For example, its called when the user clicks on a text input.",
"state": """class FocusState(rx.State):
text = "Change Me!"
def change_text(self, text):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.chakra.input(value = FocusState.text, on_focus=FocusState.change_text)""",
},
{
"name": "on_blur",
"description": "The on_blur event handler is called when focus has left the element (or left some element inside of it). For example, its called when the user clicks outside of a focused text input.",
"state": """class BlurState(rx.State):
text = "Change Me!"
def change_text(self, text):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.chakra.input(value = BlurState.text, on_blur=BlurState.change_text)""",
},
{
"name": "on_change",
"description": "The on_change event handler is called when the value of an element has changed. For example, its called when the user types into a text input each keystoke triggers the on change.",
"state": """class ChangeState(rx.State):
checked: bool = False
""",
"example": """rx.switch(on_change=ChangeState.set_checked)""",
},
{
"name": "on_click",
"description": "The on_click event handler is called when the user clicks on an element. For example, its called when the user clicks on a button.",
"state": """class ClickState(rx.State):
text = "Change Me!"
def change_text(self):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.button(ClickState.text, on_click=ClickState.change_text)""",
},
{
"name": "on_context_menu",
"description": "The on_context_menu event handler is called when the user right-clicks on an element. For example, its called when the user right-clicks on a button.",
"state": """class ContextState(rx.State):
text = "Change Me!"
def change_text(self):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.button(ContextState.text, on_context_menu=ContextState.change_text)""",
},
{
"name": "on_double_click",
"description": "The on_double_click event handler is called when the user double-clicks on an element. For example, its called when the user double-clicks on a button.",
"state": """class DoubleClickState(rx.State):
text = "Change Me!"
def change_text(self):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.button(DoubleClickState.text, on_double_click=DoubleClickState.change_text)""",
},
{
"name": "on_mount",
"description": "The on_mount event handler is called after the component is rendered on the page. It is similar to a page on_load event, although it does not necessarily fire when navigating between pages.",
"state": """class MountState(rx.State):
events: list[str] = []
def on_mount(self):
self.events = self.events[-4:] + ["on_mount @ " + str(datetime.now())]
""",
"example": """rx.vstack(rx.foreach(MountState.events, rx.text), on_mount=MountState.on_mount)""",
},
{
"name": "on_unmount",
"description": "The on_unmount event handler is called after removing the component from the page. However, on_unmount will only be called for internal navigation, not when following external links or refreshing the page.",
"state": """class UnmountState(rx.State):
events: list[str] = []
def on_unmount(self):
self.events = self.events[-4:] + ["on_unmount @ " + str(datetime.now())]
""",
"example": """rx.vstack(rx.foreach(UnmountState.events, rx.text), on_unmount=UnmountState.on_unmount)""",
},
{
"name": "on_mouse_up",
"description": "The on_mouse_up event handler is called when the user releases a mouse button on an element. For example, its called when the user releases the left mouse button on a button.",
"state": """class MouseUpState(rx.State):
text = "Change Me!"
def change_text(self):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.button(MouseUpState.text, on_mouse_up=MouseUpState.change_text)""",
},
{
"name": "on_mouse_down",
"description": "The on_mouse_down event handler is called when the user presses a mouse button on an element. For example, its called when the user presses the left mouse button on a button.",
"state": """class MouseDown(rx.State):
text = "Change Me!"
def change_text(self):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.button(MouseDown.text, on_mouse_down=MouseDown.change_text)""",
},
{
"name": "on_mouse_enter",
"description": "The on_mouse_enter event handler is called when the users mouse enters an element. For example, its called when the users mouse enters a button.",
"state": """class MouseEnter(rx.State):
text = "Change Me!"
def change_text(self):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.button(MouseEnter.text, on_mouse_enter=MouseEnter.change_text)""",
},
{
"name": "on_mouse_leave",
"description": "The on_mouse_leave event handler is called when the users mouse leaves an element. For example, its called when the users mouse leaves a button.",
"state": """class MouseLeave(rx.State):
text = "Change Me!"
def change_text(self):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.button(MouseLeave.text, on_mouse_leave=MouseLeave.change_text)""",
},
{
"name": "on_mouse_move",
"description": "The on_mouse_move event handler is called when the user moves the mouse over an element. For example, its called when the user moves the mouse over a button.",
"state": """class MouseMove(rx.State):
text = "Change Me!"
def change_text(self):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.button(MouseMove.text, on_mouse_move=MouseMove.change_text)""",
},
{
"name": "on_mouse_out",
"description": "The on_mouse_out event handler is called when the users mouse leaves an element. For example, its called when the users mouse leaves a button.",
"state": """class MouseOut(rx.State):
text = "Change Me!"
def change_text(self):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.button(MouseOut.text, on_mouse_out=MouseOut.change_text)""",
},
{
"name": "on_mouse_over",
"description": "The on_mouse_over event handler is called when the users mouse enters an element. For example, its called when the users mouse enters a button.",
"state": """class MouseOver(rx.State):
text = "Change Me!"
def change_text(self):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.button(MouseOver.text, on_mouse_over=MouseOver.change_text)""",
},
{
"name": "on_scroll",
"description": "The on_scroll event handler is called when the user scrolls the page. For example, its called when the user scrolls the page down.",
"state": """class ScrollState(rx.State):
text = "Change Me!"
def change_text(self):
if self.text == "Change Me!":
self.text = "Changed!"
else:
self.text = "Change Me!"
""",
"example": """rx.vstack(
rx.text("Scroll to make the text below change."),
rx.text(ScrollState.text),
rx.text("Scroll to make the text above change."),
on_scroll=ScrollState.change_text,
overflow = "auto",
height = "3em",
width = "100%",
)""",
},
]
for i in SYNTHETIC_EVENTS:
exec(i["state"])
def component_grid():
events = []
for event in SYNTHETIC_EVENTS:
events.append(
rx.vstack(
h1_comp(text=event["name"]),
text_comp(text=event["description"]),
docdemo(
event["example"], state=event["state"], comp=eval(event["example"])
),
align_items="left",
)
)
return rx.box(*events)
```
# Event Triggers
Components can modify the state based on user events such as clicking a button or entering text in a field.
These events are triggered by event triggers.
Event triggers are component specific and are listed in the documentation for each component.
```python eval
rx.box(
rx.chakra.divider(),
component_grid(),
)
```

View File

@ -0,0 +1,93 @@
```python exec
import reflex as rx
```
# Special Events
Reflex includes a set of built-in special events that can be utilized as event triggers
or returned from event handlers in your applications. These events enhance interactivity and user experience.
Below are the special events available in Reflex, along with explanations of their functionality:
## rx.console_log
Perform a console.log in the browser's console.
```python demo
rx.button('Log', on_click=rx.console_log('Hello World!'))
```
When triggered, this event logs a specified message to the browser's developer console.
It's useful for debugging and monitoring the behavior of your application.
## rx.redirect
Redirect the user to a new path within the application.
### Parameters
- `path`: The destination path or URL to which the user should be redirected.
- `external`: If set to True, the redirection will open in a new tab. Defaults to `False`.
```python demo
rx.vstack(
rx.button("open in tab", on_click=rx.redirect("/docs/api-reference/special-events")),
rx.button("open in new tab", on_click=rx.redirect('https://github.com/reflex-dev/reflex/', external=True))
)
```
When this event is triggered, it navigates the user to a different page or location within your Reflex application.
By default, the redirection occurs in the same tab. However, if you set the external parameter to True, the redirection
will open in a new tab or window, providing a seamless user experience.
## rx.set_clipboard
Set the specified text content to the clipboard.
```python demo
rx.button('Copy "Hello World" to clipboard',on_click=rx.set_clipboard('Hello World'),)
```
This event allows you to copy a given text or content to the user's clipboard.
It's handy when you want to provide a "Copy to Clipboard" feature in your application,
allowing users to easily copy information to paste elsewhere.
## rx.set_value
Set the value of a specified reference element.
```python demo
rx.hstack(
rx.chakra.input(id='input1'),
rx.button(
'Erase', on_click=rx.set_value('input1', '')
),
)
```
With this event, you can modify the value of a particular HTML element, typically an input field or another form element.
## rx.window_alert
Create a window alert in the browser.
```python demo
rx.button('Alert', on_click=rx.window_alert('Hello World!'))
```
## rx.download
Download a file at a given path.
Parameters:
- `url`: The URL of the file to be downloaded.
- `data`: The data to be downloaded. Should be `str` or `bytes`, `data:` URI, `PIL.Image`, or any state Var (to be converted to JSON).
- `filename`: The desired filename of the downloaded file.
```md alert
`url` and `data` args are mutually exclusive, and at least one of them must be provided.
```
```python demo
rx.button("Download", on_click=rx.download(url="/reflex_banner.png", filename="different_name_logo.png"))
```

View File

@ -0,0 +1,43 @@
```python exec
import reflex as rx
```
# Backend API Routes
In addition to your frontend app, Reflex also uses a FastAPI backend to serve your app.
To add additional endpoints to the backend API, you can use `app.add_api_route` and add a route that returns JSON.
```python
async def api_test(item_id: int):
return \{"my_result": item_id}
app = rx.App()
app.api.add_api_route("/items/\{item_id}", api_test)
```
Now you can access the endpoint at `localhost:8000/items/23` and get the result.
This is useful for creating a backend API that can be used for purposes other than your Reflex app.
## Reserved Routes
Some routes on the backend are reserved for the runtime of Reflex, and should not be overriden unless you know what you are doing.
## Ping
`localhost:8000/ping/`: You can use this route to check the health of the backend.
The expected return is `"pong"`.
## Event
`localhost:8000/_event`: the frontend will use this route to notify the backend that an event occurred.
```md alert error
# Overriding this route will break the event communication
```
## Upload
`localhost:8000/_upload`: This route is used for the upload of file when using `rx.upload()`.

View File

@ -0,0 +1,42 @@
```python exec
import reflex as rx
```
# Assets
Static files such as images and stylesheets can be placed in `"assets/"` folder of the project. These files can be referenced within your app.
```md alert
# Assets are copied during the build process.
Any files placed within the `assets/` folder at runtime will not be available to the app
when running in production mode. The `assets/` folder should only be used for static files.
```
## Referencing Assets
To reference an image in the `"assets/"` simply pass the relative path as a prop.
For example, you can store your logo in your assets folder:
```bash
assets
└── Reflex.svg
```
Then you can display it using a `rx.image` component:
```python demo
rx.image(src="/Reflex.svg", width="5em")
```
```md alert
Always prefix the asset path with a forward slash `/` to reference the asset from the root of the project,
or it may not display correctly on non-root pages.
```
## Favicon
The favicon is the small icon that appears in the browser tab.
You can add a `"favicon.ico"` file to the `"assets/"` folder to change the favicon.

View File

@ -0,0 +1,89 @@
```python exec
import reflex as rx
from pcweb.pages.docs import library
from pcweb.pages.docs import api_reference
```
# Files
In addition to any assets you ship with your app, many web app will often need to receive or send files, whether you want to share medias, allow user to import their data, or export some backend data.
In this section, we will cover all you need to know for manipulating files in Reflex.
## Download
If you want to let the users of your app download files from your server to their computer, Reflex offer you two way.
### With a regular link
For some basic usage, simply providing the path to your resource in a `rx.link` will work, and clicking the link will download or display the resource.
```python demo
rx.link("Download", href="/reflex_banner.png")
```
### With `rx.download` event
Using the `rx.download` event will always prompt the browser to download the file, even if it could be displayed in the browser.
The `rx.download` event also allows the download to be triggered from another backend event handler.
```python demo
rx.button(
"Download",
on_click=rx.download(url="/reflex_banner.png"),
)
```
`rx.download` lets you specify a name for the file that will be downloaded, if you want it to be different from the name on the server side.
```python demo
rx.button(
"Download and Rename",
on_click=rx.download(
url="/reflex_banner.png",
filename="different_name_logo.png"
),
)
```
If the data to download is not already available at a known URL, pass the `data` directly to the `rx.download` event from the backend.
```python demo exec
import random
class DownloadState(rx.State):
def download_random_data(self):
return rx.download(
data=",".join([str(random.randint(0, 100)) for _ in range(10)]),
filename="random_numbers.csv"
)
def download_random_data_button():
return rx.button(
"Download random numbers",
on_click=DownloadState.download_random_data
)
```
The `data` arg accepts `str` or `bytes` data, a `data:` URI, `PIL.Image`, or any state Var. If the Var is not already a string, it will be converted to a string using `JSON.stringify`. This allows complex state structures to be offered as JSON downloads.
Reference page for `rx.download` [here]({api_reference.special_events.path}#rx.download).
## Upload
Uploading files to your server let your users interact with your app in a different way than just filling forms to provide data.
The component `rx.upload` let your users upload files on the server.
Here is a basic example of how it is used:
```python
def index():
return rx.fragment(
rx.upload(rx.text("Upload files"), rx.icon(tag="upload")),
rx.button(on_submit=State.<your_upload_handler>)
)
```
For detailed information, see the reference page of the component [here]({library.forms.upload.path}).

View File

@ -0,0 +1,34 @@
```python exec
import reflex as rx
```
# Client-storage
You can use the browser's local storage to persist state between sessions.
This allows user preferences, authentication cookies, other bits of information
to be stored on the client and accessed from different browser tabs.
A client-side storage var looks and acts like a normal `str` var, except the
default value is either `rx.Cookie` or `rx.LocalStorage` depending on where the
value should be stored. The key name will be based on the var name, but this
can be overridden by passing `name="my_custom_name"` as a keyword argument.
For more information see [Browser Storage](/docs/api-reference/browser/).
Try entering some values in the text boxes below and then load the page in a separate
tab or check the storage section of browser devtools to see the values saved in the browser.
```python demo exec
class ClientStorageState(rx.State):
my_cookie: str = rx.Cookie("")
my_local_storage: str = rx.LocalStorage("")
custom_cookie: str = rx.Cookie(name="CustomNamedCookie", max_age=3600)
def client_storage_example():
return rx.vstack(
rx.hstack(rx.text("my_cookie"), rx.chakra.input(value=ClientStorageState.my_cookie, on_change=ClientStorageState.set_my_cookie)),
rx.hstack(rx.text("my_local_storage"), rx.chakra.input(value=ClientStorageState.my_local_storage, on_change=ClientStorageState.set_my_local_storage)),
rx.hstack(rx.text("custom_cookie"), rx.chakra.input(value=ClientStorageState.custom_cookie, on_change=ClientStorageState.set_custom_cookie)),
)
```

View File

@ -0,0 +1,25 @@
```python exec
import reflex as rx
```
# Conditional Props
Sometimes you want to set a prop based on a condition. You can use the `rx.cond` function to do this.
```python demo exec
class PropCondState(rx.State):
value: list[int]
def set_end(self, value: int):
self.value = value
def cond_prop():
return rx.slider(
default_value=[50],
on_value_commit=PropCondState.set_end,
color_scheme=rx.cond(PropCondState.value[0] > 50, "green", "pink"),
width="100%",
)
```

View File

@ -0,0 +1,269 @@
```python exec
import reflex as rx
from pcweb.pages.docs import vars, library
```
# Conditional Rendering
We use the `cond` component to conditionally render components. The `cond` component acts in a similar way to a conditional (ternary) operator in python, acting in a similar fashion to an `if-else` statement.
```md alert
Check out the API reference for [cond docs]({library.layout.cond.path}).
```
```python eval
rx.box(height="2em")
```
Here is a simple example to show how by checking the value of the state var `show` we can render either `blue` text or `red` text.
The first argument to the `cond` component is the condition we are checking. Here the condition is the value of the state var boolean `show`.
If `show` is `True` then the 2nd argument to the `cond` component is rendered, in this case that is `rx.text("Text 1", color="blue")`.
If `show` is `False` then the 3rd argument to the `cond` component is rendered, in this case that is `rx.text("Text 2", color="red")`.
```python demo exec
class CondSimpleState(rx.State):
show: bool = True
def change(self):
self.show = not (self.show)
def cond_simple_example():
return rx.vstack(
rx.button("Toggle", on_click=CondSimpleState.change),
rx.cond(
CondSimpleState.show,
rx.text("Text 1", color="blue"),
rx.text("Text 2", color="red"),
),
)
```
## Var Operations (negation)
You can use var operations with the `cond` component. To learn more generally about var operators check out [these docs]({vars.var_operations.path}). The logical operator `~` can be used to negate a condition. In this example we show that by negating the condition `~CondNegativeState.show` within the cond, we then render the `rx.text("Text 1", color="blue")` component when the state var `show` is negative.
```python demo exec
class CondNegativeState(rx.State):
show: bool = True
def change(self):
self.show = not (self.show)
def cond_negative_example():
return rx.vstack(
rx.text(f"Value of state var show: {CondNegativeState.show}"),
rx.button("Toggle", on_click=CondNegativeState.change),
rx.cond(
CondNegativeState.show,
rx.text("Text 1", color="blue"),
rx.text("Text 2", color="red"),
),
rx.cond(
~CondNegativeState.show,
rx.text("Text 1", color="blue"),
rx.text("Text 2", color="red"),
),
)
```
## Multiple Conditions
It is also possible to make up complex conditions using the `logical or` (|) and `logical and` (&) operators.
Here we have an example using the var operators `>=`, `<=`, `&`. We define a condition that if a person has an age between 18 and 65, including those ages, they are able to work, otherwise they cannot.
We could equally use the operator `|` to represent a `logical or` in one of our conditions.
```python demo exec
import random
class CondComplexState(rx.State):
age: int = 19
def change(self):
self.age = random.randint(0, 100)
def cond_complex_example():
return rx.vstack(
rx.button("Toggle", on_click=CondComplexState.change),
rx.text(f"Age: {CondComplexState.age}"),
rx.cond(
(CondComplexState.age >= 18) & (CondComplexState.age <=65),
rx.text("You can work!", color="green"),
rx.text("You cannot work!", color="red"),
),
)
```
## Reusing Cond
We can also reuse a `cond` component several times by defining it within a function that returns a `cond`.
In this example we define the function `render_item`. This function takes in an `item`, uses the `cond` to check if the item `is_packed`. If it is packed it returns the `item_name` with a `✔` next to it, and if not then it just returns the `item_name`.
```python demo exec
class ToDoListItem(rx.Base):
item_name: str
is_packed: bool
class CondRepeatState(rx.State):
to_do_list: list[ToDoListItem] = [
ToDoListItem(item_name="Space suit", is_packed=True),
ToDoListItem(item_name="Helmet", is_packed=True),
ToDoListItem(item_name="Back Pack", is_packed=False),
]
def render_item(item: [str, bool]):
return rx.cond(
item.is_packed,
rx.chakra.list_item(item.item_name + ' ✔'),
rx.chakra.list_item(item.item_name),
)
def packing_list():
return rx.vstack(
rx.text("Sammy's Packing List"),
rx.chakra.list(rx.foreach(CondRepeatState.to_do_list, render_item)),
)
```
## Nested Conditional
We can also nest `cond` components within each other to create more complex logic. In python we can have an `if` statement that then has several `elif` statements before finishing with an `else`. This is also possible in reflex using nested `cond` components. In this example we check whether a number is positive, negative or zero.
Here is the python logic using `if` statements:
```python
number = 0
if number > 0:
print("Positive number")
elif number == 0:
print('Zero')
else:
print('Negative number')
```
This reflex code that is logically identical:
```python demo exec
import random
class NestedState(rx.State):
num: int = 0
def change(self):
self.num = random.randint(-10, 10)
def cond_nested_example():
return rx.vstack(
rx.button("Toggle", on_click=NestedState.change),
rx.cond(
NestedState.num > 0,
rx.text(f"{NestedState.num} is Positive!", color="orange"),
rx.cond(
NestedState.num == 0,
rx.text(f"{NestedState.num} is Zero!", color="blue"),
rx.text(f"{NestedState.num} is Negative!", color="red"),
)
),
)
```
Here is a more advanced example where we have three numbers and we are checking which of the three is the largest. If any two of them are equal then we return that `Some of the numbers are equal!`.
The reflex code that follows is logically identical to doing the following in python:
```python
a = 8
b = 10
c = 2
if((a>b and a>c) and (a != b and a != c)):
print(a, " is the largest!")
elif((b>a and b>c) and (b != a and b != c)):
print(b, " is the largest!")
elif((c>a and c>b) and (c != a and c != b)):
print(c, " is the largest!")
else:
print("Some of the numbers are equal!")
```
```python demo exec
import random
class CNS(rx.State):
# CNS: CondNestedState
a: int = 8
b: int = 10
c: int = 2
def change(self):
self.a = random.randint(0, 10)
self.b = random.randint(0, 10)
self.c = random.randint(0, 10)
def cond_nested_example_2():
return rx.vstack(
rx.button("Toggle", on_click=CNS.change),
rx.text(f"a: {CNS.a}, b: {CNS.b}, c: {CNS.c}"),
rx.cond(
((CNS.a > CNS.b) & (CNS.a > CNS.c)) & ((CNS.a != CNS.b) & (CNS.a != CNS.c)),
rx.text(f"{CNS.a} is the largest!", color="green"),
rx.cond(
((CNS.b > CNS.a) & (CNS.b > CNS.c)) & ((CNS.b != CNS.a) & (CNS.b != CNS.c)),
rx.text(f"{CNS.b} is the largest!", color="orange"),
rx.cond(
((CNS.c > CNS.a) & (CNS.c > CNS.b)) & ((CNS.c != CNS.a) & (CNS.c != CNS.b)),
rx.text(f"{CNS.c} is the largest!", color="blue"),
rx.text("Some of the numbers are equal!", color="red"),
),
),
),
)
```
## Cond used as a style prop
`Cond` can also be used to show and hide content in your reflex app. In this example, we have no third argument to the `cond` operator which means that nothing is rendered if the condition is false.
```python demo exec
class CondStyleState(rx.State):
show: bool = False
img_url: str = "/preview.png"
def change(self):
self.show = not (self.show)
def cond_style_example():
return rx.vstack(
rx.button("Toggle", on_click=CondStyleState.change),
rx.cond(
CondStyleState.show,
rx.image(
src=CondStyleState.img_url,
height="25em",
width="25em",
),
),
)
```

81
docs/components/props.md Normal file
View File

@ -0,0 +1,81 @@
```python exec
from pcweb.pages.docs.library import library
from pcweb.pages.docs import state, vars
import reflex as rx
```
# Props
Props modify the behavior and appearance of a component. They are passed in as keyword arguments to the component function.
## Component Props
Each component has props that are specific to that component. For example, the `rx.avatar` component has a fallback prop that sets the `fallback` of the avatar.
```python demo
rx.avatar(
fallback="JD"
)
```
Check the docs for the component you are using to see what props are available.
```md alert success
# Reflex has a wide selection of [built-in components]({library.path}) to get you started quickly.
```
## HTML Props
Components support many standard HTML properties as props. For example: the HTML [id]({"https://www.w3schools.com/html/html_id.asp"}) property is exposed directly as the prop `id`. The HTML [className]({"https://www.w3schools.com/jsref/prop_html_classname.asp"}) property is exposed as the prop `class_name` (note the Pythonic snake_casing!).
```python demo
rx.box(
"Hello World",
id="box-id",
class_name=["class-name-1", "class-name-2",],
)
```
## Binding Props to State
Reflex apps can have a [State]({state.overview.path}) that stores all variables that can change when the app is running, as well as the event handlers that can change those variables.
State may be modified in response to things like user input like clicking a button, or in response to events like loading a page.
State vars can be bound to component props, so that the UI always reflects the current state of the app.
```md alert warning
Optional: Learn all about [State]({state.overview.path}) first.
```
You can set the value of a prop to a [state var]({vars.base_vars.path}) to make the component update when the var changes.
Try clicking the badge below to change its color.
```python demo exec
class PropExampleState(rx.State):
text: str = "Hello World"
color: str = "red"
def flip_color(self):
if self.color == "red":
self.color = "blue"
else:
self.color = "red"
def index():
return rx.badge(
PropExampleState.text,
color_scheme=PropExampleState.color,
on_click=PropExampleState.flip_color,
font_size="1.5em",
_hover={
"cursor": "pointer",
}
)
```
In this example, the `color_scheme` prop is bound to the `color` state var.
When the `flip_color` event handler is called, the `color` var is updated, and the `color_scheme` prop is updated to match.

View File

@ -0,0 +1,197 @@
```python exec
import reflex as rx
from pcweb.pages.docs import vars
```
# Rendering Iterables
You will often want to display multiple similar components from a collection of data. The `rx.foreach` component takes an `iterable` (list, tuple or dict) and a `function` that renders each item in the list. This is useful for dynamically rendering a list of items defined in a state.
In this first simple example we iterate through a `list` of colors and render the name of the color and use this color as the background for that `rx.box`. As we can see we have a function `colored_box` that we pass to the `rx.foreach` component. This function renders each item from the `list` that we have defined as a state var `color`.
```python demo exec
class IterState(rx.State):
color: list[str] = [
"red",
"green",
"blue",
"yellow",
"orange",
"purple",
]
def colored_box(color: str):
return rx.box(rx.text(color), background_color=color)
def simple_foreach():
return rx.chakra.responsive_grid(
rx.foreach(IterState.color, colored_box),
columns=[2, 4, 6],
)
```
```md alert warning
# The type signature of the functions does not matter to the `foreach` component. It's the type annotation on the `state var` that determines what operations are available (e.g. when nesting).
```
## Enumeration
The function can also take an index as a second argument, meaning that we can enumerate through data as shown in the example below.
```python demo exec
class IterIndexState(rx.State):
color: list[str] = [
"red",
"green",
"blue",
"yellow",
"orange",
"purple",
]
def enumerate_foreach():
return rx.chakra.responsive_grid(
rx.foreach(
IterIndexState.color,
lambda color, index: rx.box(rx.text(index), bg=color)
),
columns=[2, 4, 6],
)
```
## Dictionary
We can iterate through a `dict` data structure using a `foreach`. When the dict is passed through to the function that renders each item, it is presented as a list of key-value pairs `[("sky", "blue"), ("balloon", "red"), ("grass", "green")]`.
```python demo exec
class SimpleDictIterState(rx.State):
color_chart: dict[str, str] = {
"sky": "blue",
"balloon": "red",
"grass": "green",
}
def display_color(color: list):
# color is presented as a list key-value pairs [("sky", "blue"), ("balloon", "red"), ("grass", "green")]
return rx.box(rx.text(color[0]), bg=color[1], padding_x="1.5em")
def dict_foreach():
return rx.chakra.responsive_grid(
rx.foreach(
SimpleDictIterState.color_chart,
display_color,
),
columns=[2, 4, 6],
)
```
## Nested examples
`rx.foreach` can be used with nested state vars. Here we use nested `foreach` components to render the nested state vars. The `rx.foreach(project["technologies"], get_badge)` inside of the `project_item` function, renders the `dict` values which are of type `list`. The `rx.box(rx.foreach(NestedStateFE.projects, project_item))` inside of the `projects_example` function renders each `dict` inside of the overall state var `projects`.
```python demo exec
class NestedStateFE(rx.State):
projects: list[dict[str, list]] = [
{
"technologies": ["Next.js", "Prisma", "Tailwind", "Google Cloud", "Docker", "MySQL"]
},
{
"technologies": ["Python", "Flask", "Google Cloud", "Docker"]
}
]
def get_badge(technology: str) -> rx.Component:
return rx.chakra.badge(technology, variant="subtle", color_scheme="green")
def project_item(project: dict) -> rx.Component:
return rx.box(
rx.hstack(
rx.foreach(project["technologies"], get_badge)
),
)
def projects_example() -> rx.Component:
return rx.box(rx.foreach(NestedStateFE.projects, project_item))
```
If you want an example where not all of the values in the dict are the same type then check out the example on [var operations using foreach]({vars.var_operations.path}).
Here is a further example of how to use `foreach` with a nested data structure.
```python demo exec
class NestedDictIterState(rx.State):
color_chart: dict[str, list[str]] = {
"purple": ["red", "blue"],
"orange": ["yellow", "red"],
"green": ["blue", "yellow"],
}
def display_colors(color: list[str, list[str]]):
return rx.vstack(
rx.text(color[0], color=color[0]),
rx.hstack(
rx.foreach(
color[1],
lambda x: rx.box(
rx.text(x, color="black"), bg=x
),
)
),
)
def nested_dict_foreach():
return rx.chakra.responsive_grid(
rx.foreach(
NestedDictIterState.color_chart,
display_colors,
),
columns=[2, 4, 6],
)
```
## Foreach with Cond
We can also use `foreach` with the `cond` component.
In this example we define the function `render_item`. This function takes in an `item`, uses the `cond` to check if the item `is_packed`. If it is packed it returns the `item_name` with a `✔` next to it, and if not then it just returns the `item_name`. We use the `foreach` to iterate over all of the items in the `to_do_list` using the `render_item` function.
```python demo exec
class ToDoListItem(rx.Base):
item_name: str
is_packed: bool
class ForeachCondState(rx.State):
to_do_list: list[ToDoListItem] = [
ToDoListItem(item_name="Space suit", is_packed=True),
ToDoListItem(item_name="Helmet", is_packed=True),
ToDoListItem(item_name="Back Pack", is_packed=False),
]
def render_item(item: [str, bool]):
return rx.cond(
item.is_packed,
rx.chakra.list_item(item.item_name + ' ✔'),
rx.chakra.list_item(item.item_name),
)
def packing_list():
return rx.vstack(
rx.text("Sammy's Packing List"),
rx.chakra.list(rx.foreach(ForeachCondState.to_do_list, render_item)),
)
```

View File

@ -0,0 +1,25 @@
```python exec
from pcweb.pages.docs import styling
import reflex as rx
```
# Style Props
In addition to component-specific props, most built-in components support a full range of style props. You can use any CSS property to style a component.
```python demo
rx.button(
"Fancy Button",
border_radius="1em",
box_shadow="rgba(151, 65, 252, 0.8) 0 15px 30px -10px",
background_image="linear-gradient(144deg,#AF40FF,#5B42F3 50%,#00DDEB)",
box_sizing="border-box",
color="white",
opacity= 1,
_hover={
"opacity": .5,
}
)
```
See the [styling docs]({styling.overview.path}) to learn more about customizing the appearance of your app.

75
docs/database/overview.md Normal file
View File

@ -0,0 +1,75 @@
# Database
Reflex uses [sqlmodel](https://sqlmodel.tiangolo.com) to provide a built-in ORM wrapping SQLAlchemy.
The examples on this page refer specifically to how Reflex uses various tools to
expose an integrated database interface. Only basic use cases will be covered
below, but you can refer to the
[sqlmodel tutorial](https://sqlmodel.tiangolo.com/tutorial/select/)
for more examples and information, just replace `SQLModel` with `rx.Model` and
`Session(engine)` with `rx.session()`
For advanced use cases, please see the
[SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/orm/quickstart.html) (v1.4).
## Connecting
Reflex provides a built-in SQLite database for storing and retrieving data.
You can connect to your own SQL compatible database by modifying the
`rxconfig.py` file with your database url.
```python
config = rx.Config(
app_name="my_app",
db_url="sqlite:///reflex.db",
)
```
For more examples of database URLs that can be used, see the [SQLAlchemy
docs](https://docs.sqlalchemy.org/en/14/core/engines.html#backend-specific-urls).
Be sure to install the appropriate DBAPI driver for the database you intend to
use.
## Tables
To create a table make a class that inherits from `rx.Model` with and specify
that it is a table.
```python
class User(rx.Model, table=True):
username: str
email: str
password: str
```
## Migrations
Reflex leverages [alembic](https://alembic.sqlalchemy.org/en/latest/)
to manage database schema changes.
Before the database feature can be used in a new app you must call `reflex db init`
to initialize alembic and create a migration script with the current schema.
After making changes to the schema, use
`reflex db makemigrations --message 'something changed'`
to generate a script in the `alembic/versions` directory that will update the
database schema. It is recommended that scripts be inspected before applying
them.
The `reflex db migrate` command is used to apply migration scripts to bring the
database up to date. During app startup, if Reflex detects that the current
database schema is not up to date, a warning will be displayed on the console.
## Queries
To query the database you can create a `rx.session()`
which handles opening and closing the database connection.
You can use normal SQLAlchemy queries to query the database.
```python
with rx.session() as session:
session.add(User(username="test", email="admin@pynecone.io", password="admin"))
session.commit()
```

186
docs/database/queries.md Normal file
View File

@ -0,0 +1,186 @@
# Queries
Queries are used to retrieve data from a database.
A query is a request for information from a database table or combination of
tables. A query can be used to retrieve data from a single table or multiple
tables. A query can also be used to insert, update, or delete data from a table.
## Session
To execute a query you must first create a `rx.session`. You can use the session
to query the database using SQLModel or SQLAlchemy syntax.
The `rx.session` statement will automatically close the session when the code
block is finished. **If `session.commit()` is not called, the changes will be
rolled back and not persisted to the database.** The code can also explicitly
rollback without closing the session via `session.rollback()`.
The following example shows how to create a session and query the database.
First we create a table called `User`.
```python
class User(rx.Model, table=True):
username: str
email: str
```
### Select
Then we create a session and query the User table.
```python
class QueryUser(rx.State):
name: str
users: list[User]
def get_users(self):
with rx.session() as session:
self.users = session.exec(
User.select.where(
User.username.contains(self.name)).all())
```
The `get_users` method will query the database for all users that contain the
value of the state var `name`.
On older python versions, the `.select` attribute on model objects does not work, but
you may use `sqlmodel.select(User)` instead.
### Insert
Similarly, the `session.add()` method to add a new record to the
database or persist an existing object.
```python
class AddUser(rx.State):
username: str
email: str
def add_user(self):
with rx.session() as session:
session.add(User(username=self.username, email=self.email))
session.commit()
```
### Update
To update the user, first query the database for the object, make the desired
modifications, `.add` the object to the session and finally call `.commit()`.
```python
class ChangeEmail(rx.State):
username: str
email: str
def modify_user(self):
with rx.session() as session:
user = session.exec(User.select.where(
(User.username == self.username).first()))
user.email = self.email
session.add(user)
session.commit()
```
### Delete
To delete a user, first query the database for the object, then call
`.delete()` on the session and finally call `.commit()`.
```python
class RemoveUser(rx.State):
username: str
def delete_user(self):
with rx.session() as session:
user = session.exec(User.select.where(
User.username == self.username).first())
session.delete(user)
session.commit()
```
## ORM Object Lifecycle
The objects returned by queries are bound to the session that created them, and cannot generally
be used outside that session. After adding or updating an object, not all fields are automatically
updated, so accessing certain attributes may trigger additional queries to refresh the object.
To avoid this, the `session.refresh()` method can be used to update the object explicitly and
ensure all fields are up to date before exiting the session.
```python
class AddUserForm(rx.State):
user: User | None = None
def add_user(self, form_data: dict[str, str]):
with rx.session() as session:
self.user = User(**form_data)
session.add(self.user)
session.commit()
session.refresh(self.user)
```
Now the `self.user` object will have a correct reference to the autogenerated
primary key, `id`, even though this was not provided when the object was created
from the form data.
If `self.user` needs to be modified or used in another query in a new session,
it must be added to the session. Adding an object to a session does not
necessarily create the object, but rather associates it with a session where it
may either be created or updated accordingly.
```python
class AddUserForm(rx.State):
...
def update_user(self, form_data: dict[str, str]):
if self.user is None:
return
with rx.session() as session:
self.user.set(**form_data)
session.add(self.user)
session.commit()
session.refresh(self.user)
```
If an ORM object will be referenced and accessed outside of a session, you
should call `.refresh()` on it to avoid stale object exceptions.
## Using SQL Directly
Avoiding SQL is one of the main benefits of using an ORM, but sometimes it is
necessary for particularly complex queries, or when using database-specific
features.
SQLModel exposes the `session.execute()` method that can be used to execute raw
SQL strings. If parameter binding is needed, the query may be wrapped in
[`sqlalchemy.text`](https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.text),
which allows colon-prefix names to be used as placeholders.
```md alert
Never use string formatting to construct SQL queries, as this may lead to SQL injection vulnerabilities in the app.
```
```python
import sqlalchemy
import reflex as rx
class State(rx.State):
def insert_user_raw(self, username, email):
with rx.session() as session:
session.execute(
sqlalchemy.text(
"INSERT INTO user (username, email) "
"VALUES (:username, :email)"
),
\{"username": username, "email": email},
)
session.commit()
@rx.var
def raw_user_tuples(self) -> list[list]:
with rx.session() as session:
return [list(row) for row in session.execute("SELECT * FROM user").all()]
```

View File

@ -0,0 +1,162 @@
# Relationships
Foreign key relationships are used to link two tables together. For example,
the `Post` model may have a field, `user_id`, with a foreign key of `user.id`,
referencing a `User` model. This would allow us to automatically query the `Post` objects
associated with a user, or find the `User` object associated with a `Post`.
To establish bidirectional relationships a model must correctly set the
`back_populates` keyword argument on the `Relationship` to the relationship
attribute in the _other_ model.
## Foreign Key Relationships
To create a relationship, first add a field to the model that references the
primary key of the related table, then add a `sqlmodel.Relationship` attribute
which can be used to access the related objects.
Defining relationships like this requires the use of `sqlmodel` objects as
seen in the example.
```python
from typing import List, Optional
import sqlmodel
import reflex as rx
class Post(rx.Model, table=True):
title: str
body: str
user_id: int = sqlmodel.Field(foreign_key="user.id")
user: Optional["User"] = sqlmodel.Relationship(back_populates="posts")
flags: Optional[List["Flag"]] = sqlmodel.Relationship(back_populates="post")
class User(rx.Model, table=True):
username: str
email: str
posts: List[Post] = sqlmodel.Relationship(back_populates="user")
flags: List["Flag"] = sqlmodel.Relationship(back_populates="user")
class Flag(rx.Model, table=True):
post_id: int = sqlmodel.Field(foreign_key="post.id")
user_id: int = sqlmodel.Field(foreign_key="user.id")
message: str
post: Optional[Post] = sqlmodel.Relationship(back_populates="flags")
user: Optional[User] = sqlmodel.Relationship(back_populates="flags")
```
See the [SQLModel Relationship Docs](https://sqlmodel.tiangolo.com/tutorial/relationship-attributes/define-relationships-attributes/) for more details.
## Querying Relationships
### Inserting Linked Objects
The following example assumes that the flagging user is stored in the state as a
`User` instance and that the post `id` is provided in the data submitted in the
form.
```python
class FlagPostForm(rx.State):
user: User
def flag_post(self, form_data: dict[str, str]):
with rx.session() as session:
post = session.get(Post, int(form_data.pop("post_id")))
flag = Flag(message=form_data.pop("message"), post=post, user=self.user)
session.add(flag)
session.commit()
```
### How are Relationships Dereferenced?
By default, the relationship attributes are in **lazy loading** or `"select"`
mode, which generates a query _on access_ to the relationship attribute. Lazy
loading is generally fine for single object lookups and manipulation, but can be
inefficient when accessing many linked objects for serialization purposes.
There are several alternative loading mechanisms available that can be set on
the relationship object or when performing the query.
* "joined" or `joinload` - generates a single query to load all related objects
at once.
* "subquery" or `subqueryload` - generates a single query to load all related
objects at once, but uses a subquery to do the join, instead of a join in the
main query.
* "selectin" or `selectinload` - emits a second (or more) SELECT statement which
assembles the primary key identifiers of the parent objects into an IN clause,
so that all members of related collections / scalar references are loaded at
once by primary key
There are also non-loading mechanisms, "raise" and "noload" which are used to
specifically avoid loading a relationship.
Each loading method comes with tradeoffs and some are better suited for different
data access patterns.
See [SQLAlchemy: Relationship Loading Techniques](https://docs.sqlalchemy.org/en/14/orm/loading_relationships.html)
for more detail.
### Querying Linked Objects
To query the `Post` table and include all `User` and `Flag` objects up front,
the `.options` interface will be used to specify `selectinload` for the required
relationships. Using this method, the linked objects will be available for
rendering in frontend code without additional steps.
```python
import sqlalchemy
class PostState(rx.State):
posts: List[Post]
def load_posts(self):
with rx.session() as session:
self.posts = session.exec(
Post.select
.options(
sqlalchemy.orm.selectinload(Post.user),
sqlalchemy.orm.selectinload(Post.flags).options(
sqlalchemy.orm.selectinload(Flag.user),
),
)
.limit(15)
).all()
```
The loading methods create new query objects and thus may be linked if the
relationship itself has other relationships that need to be loaded. In this
example, since `Flag` references `User`, the `Flag.user` relationship must be
chain loaded from the `Post.flags` relationship.
### Specifying the Loading Mechanism on the Relationship
Alternatively, the loading mechanism can be specified on the relationship by
passing `sa_relationship_kwargs=\{"lazy": method}` to `sqlmodel.Relationship`,
which will use the given loading mechanism in all queries by default.
```python
from typing import List, Optional
import sqlmodel
import reflex as rx
class Post(rx.Model, table=True):
...
user: Optional["User"] = sqlmodel.Relationship(
back_populates="posts",
sa_relationship_kwargs=\{"lazy": "selectin"},
)
flags: Optional[List["Flag"]] = sqlmodel.Relationship(
back_populates="post",
sa_relationship_kwargs=\{"lazy": "selectin"},
)
```

70
docs/database/tables.md Normal file
View File

@ -0,0 +1,70 @@
# Tables
Tables are database objects that contain all the data in a database.
In tables, data is logically organized in a row-and-column format similar to a
spreadsheet. Each row represents a unique record, and each column represents a
field in the record.
## Creating a Table
To create a table make a class that inherits from `rx.Model`.
The following example shows how to create a table called `User`.
```python
class User(rx.Model, table=True):
username: str
email: str
```
The `table=True` argument tells Reflex to create a table in the database for
this class.
### Primary Key
By default, Reflex will create a primary key column called `id` for each table.
However, if an `rx.Model` defines a different field with `primary_key=True`, then the
default `id` field will not be created. A table may also redefine `id` as needed.
It is not currently possible to create a table without a primary key.
## Advanced Column Types
SQLModel automatically maps basic python types to SQLAlchemy column types, but
for more advanced use cases, it is possible to define the column type using
`sqlalchemy` directly. For example, we can add a last updated timestamp to the
post example as a proper `DateTime` field with timezone.
```python
import datetime
import sqlmodel
import sqlalchemy
class Post(rx.Model, table=True):
...
update_ts: datetime.datetime = sqlmodel.Field(
default=None,
sa_column=sqlalchemy.Column(
"update_ts",
sqlalchemy.DateTime(timezone=True),
server_default=sqlalchemy.func.now(),
),
)
```
To make the `Post` model more usable on the frontend, a `dict` method may be provided
that converts any fields to a JSON serializable value. In this case, the dict method is
overriding the default `datetime` serializer to strip off the microsecond part.
```python
class Post(rx.Model, table=True):
...
def dict(self, *args, **kwargs) -> dict:
d = super().dict(*args, **kwargs)
d["update_ts"] = self.update_ts.replace(microsecond=0).isoformat()
return d
```

View File

@ -0,0 +1,223 @@
```python exec
import reflex as rx
from docs.datatable_tutorial.datatable_tutorial_utils import DataTableState, DataTableState2
```
# Adding Interactivity to our DataTable
Now we will add interactivity to our datatable. We do this using event handlers and event triggers.
The first example implements a handler for the `on_cell_clicked` event trigger, which is called when the user clicks on a cell of the data editor. The event trigger receives the coordinates of the cell.
```python
class DataTableState(rx.State):
clicked_cell: str = "Cell clicked: "
...
def get_clicked_data(self, pos: tuple[int, int]) -> str:
self.clicked_cell = f"Cell clicked: \{pos}"
```
The state has a var called `clicked_cell` that will store a message about which cell was clicked. We define an event handler `get_clicked_data` that updates the value of the `clicked_cell` var when it is called. In essence, we have clicked on a cell, called the `on_cell_clicked` event trigger which calls the `get_clicked_data` event handler, which updates the `clicked_cell` var.
```python demo
rx.text(DataTableState.clicked_cell)
```
```python demo
rx.data_editor(
columns=DataTableState.cols,
data=DataTableState.data,
on_cell_clicked=DataTableState.get_clicked_data,
)
```
The event handler `on_cell_context_menu` can be used in the same way as `on_cell_clicked`, except here the event trigger is called when the user right clicks, i.e. when the cell should show a context menu.
## Editing cells
Another important type of interactivity we will showcase is how to edit cells. Here we use the `on_cell_edited` event trigger to update the data based on what the user entered.
```python
class DataTableState(rx.State):
clicked_cell: str = "Cell clicked: "
edited_cell: str = "Cell edited: "
...
def get_clicked_data(self, pos) -> str:
self.clicked_cell = f"Cell clicked: \{pos}"
def get_edited_data(self, pos, val) -> str:
col, row = pos
self.data[row][col] = val["data"]
self.edited_cell = f"Cell edited: \{pos}, Cell value: \{val["data"]}"
```
The `on_cell_edited` event trigger is called when the user modifies the content of a cell. It receives the coordinates of the cell and the modified content. We pass these into the `get_edited_data` event handler and use them to update the `data` state var at the appropriate position. We then update the `edited_cell` var value.
```python demo
rx.text(DataTableState.edited_cell)
```
```python demo
rx.data_editor(
columns=DataTableState.cols,
data=DataTableState.data,
on_cell_clicked=DataTableState.get_clicked_data,
on_cell_edited=DataTableState.get_edited_data,
)
```
## Group Header
We can define group headers which are headers that encompass a group of columns. We define these in the `columns` using the `group` property such as `"group": "Data"`. The `columns` would now be defined as below. Only the `Title` does not fall under a group header, all the rest fall under the `Data` group header.
```python
class DataTableState2(rx.State):
"""The app state."""
...
cols: list[dict] = [
{\"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,
},
]
...
```
The table now has a header as below.
```python demo
rx.data_editor(
columns=DataTableState2.cols,
data=DataTableState2.data,
on_cell_clicked=DataTableState2.get_clicked_data,
on_cell_edited=DataTableState2.get_edited_data,
)
```
There are several event triggers we can apply to the group header.
```python
class DataTableState2(rx.State):
"""The app state."""
right_clicked_group_header : str = "Group header right clicked: "
...
def get_group_header_right_click(self, index, val):
self.right_clicked_group_header = f"Group header right clicked at index: \{index}, Group header value: \{val['group']}"
```
```python demo
rx.text(DataTableState2.right_clicked_group_header)
```
```python demo
rx.data_editor(
columns=DataTableState2.cols,
data=DataTableState2.data,
on_cell_clicked=DataTableState2.get_clicked_data,
on_cell_edited=DataTableState2.get_edited_data,
on_group_header_context_menu=DataTableState2.get_group_header_right_click,
)
```
In this example we use the `on_group_header_context_menu` event trigger which is called when the user right-clicks on a group header. It returns the `index` and the `data` of the group header. We can also use the `on_group_header_clicked` and `on_group_header_renamed` event triggers which are called when the user left-clicks on a group header and when a user renames a group header respectively.
## More Event Triggers
There are several other event triggers that are worth exploring. The `on_item_hovered` event trigger is called whenever the user hovers over an item in the datatable. The `on_delete` event trigger is called when the user deletes a cell from the datatable.
The final event trigger to check out is `on_column_resize`. `on_column_resize` allows us to respond to the user dragging the handle between columns. The event trigger returns the `col` we are adjusting and the new `width` we have defined. The `col` that is returned is a dictionary for example: `\{'title': 'Name', 'type': 'str', 'group': 'Data', 'width': 198, 'pos': 1}`. We then index into `self.cols` defined in our state and change the `width` of that column using this code: `self.cols[col['pos']]['width'] = width`.
```python
class DataTableState2(rx.State):
"""The app state."""
...
item_hovered: str = "Item Hovered: "
deleted: str = "Deleted: "
...
def get_item_hovered(self, pos) -> str:
self.item_hovered = f"Item Hovered type: \{pos['kind']}, Location: \{pos['location']}"
def get_deleted_item(self, selection):
self.deleted = f"Deleted cell: \{selection['current']['cell']}"
def column_resize(self, col, width):
self.cols[col['pos']]['width'] = width
```
```python demo
rx.text(DataTableState2.item_hovered)
```
```python demo
rx.text(DataTableState2.deleted)
```
```python demo
rx.data_editor(
columns=DataTableState2.cols,
data=DataTableState2.data,
on_cell_clicked=DataTableState2.get_clicked_data,
on_cell_edited=DataTableState2.get_edited_data,
on_group_header_context_menu=DataTableState2.get_group_header_right_click,
on_item_hovered=DataTableState2.get_item_hovered,
on_delete=DataTableState2.get_deleted_item,
on_column_resize=DataTableState2.column_resize,
)
```

View File

@ -0,0 +1,141 @@
```python exec
import reflex as rx
from docs.datatable_tutorial.datatable_tutorial_utils import DataTableState, DataTableState2
from pcweb.pages.docs import library
```
# DataTable Styling
There are props that we can explore to ensure the datatable is shaped correctly and reacts in the way we expect. We can set `on_paste` to `True`, which allows us to paste directly into a cell. We can use `draw_focus_ring` to draw a ring around the cells when selected, this defaults to `True` so can be turned off if we do not want it. The `rows` prop can be used to hard code the number of rows that we show.
`freeze_columns` is used to keep a certain number of the left hand columns frozen when scrolling horizontally. `group_header_height` and `header_height` define the height of the group header and the individual headers respectively. `max_column_width` and `min_column_width` define how large or small the columns are allowed to be with the manual column resizing. We can also define the `row_height` to make the rows more nicely spaced.
We can add `row_markers`, which appear on the furthest left side of the table. They can take values of `'none', 'number', 'checkbox', 'both', 'clickable-number'`. We can set `smooth_scroll_x` and `smooth_scroll_y`, which allows us to smoothly scroll along the columns and rows.
By default there is a `vertical_border` between the columns, we can turn it off by setting this prop to `False`. We can define how many columns a user can select at a time by setting the `column_select` prop. It can take values of `"none", "single", "multi"`.
We can allow `overscroll_x`, which allows users to scroll past the limit of the actual horizontal content. There is an equivalent `overscroll_y`.
Check out [these docs]({library.datadisplay.data_editor.path}) for more information on these props.
```python demo
rx.data_editor(
columns=DataTableState2.cols,
data=DataTableState2.data,
#rows=4,
on_paste=True,
draw_focus_ring=False,
freeze_columns=2,
group_header_height=50,
header_height=60,
max_column_width=300,
min_column_width=100,
row_height=50,
row_markers='clickable-number',
smooth_scroll_x=True,
vertical_border=False,
column_select="multi",
overscroll_x=100,
on_cell_clicked=DataTableState2.get_clicked_data,
on_cell_edited=DataTableState2.get_edited_data,
on_group_header_context_menu=DataTableState2.get_group_header_right_click,
on_item_hovered=DataTableState2.get_item_hovered,
on_delete=DataTableState2.get_deleted_item,
on_column_resize=DataTableState2.column_resize,
)
```
## Theming
Lastly there is a `theme` prop that allows us to pass in all color and font information for the data table.
```python
darkTheme = {
"accentColor": "#8c96ff",
"accentLight": "rgba(202, 206, 255, 0.253)",
"textDark": "#ffffff",
"textMedium": "#b8b8b8",
"textLight": "#a0a0a0",
"textBubble": "#ffffff",
"bgIconHeader": "#b8b8b8",
"fgIconHeader": "#000000",
"textHeader": "#a1a1a1",
"textHeaderSelected": "#000000",
"bgCell": "#16161b",
"bgCellMedium": "#202027",
"bgHeader": "#212121",
"bgHeaderHasFocus": "#474747",
"bgHeaderHovered": "#404040",
"bgBubble": "#212121",
"bgBubbleSelected": "#000000",
"bgSearchResult": "#423c24",
"borderColor": "rgba(225,225,225,0.2)",
"drilldownBorder": "rgba(225,225,225,0.4)",
"linkColor": "#4F5DFF",
"headerFontStyle": "bold 14px",
"baseFontStyle": "13px",
"fontFamily": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif",
}
```
```python exec
darkTheme = {
"accentColor": "#8c96ff",
"accentLight": "rgba(202, 206, 255, 0.253)",
"textDark": "#ffffff",
"textMedium": "#b8b8b8",
"textLight": "#a0a0a0",
"textBubble": "#ffffff",
"bgIconHeader": "#b8b8b8",
"fgIconHeader": "#000000",
"textHeader": "#a1a1a1",
"textHeaderSelected": "#000000",
"bgCell": "#16161b",
"bgCellMedium": "#202027",
"bgHeader": "#212121",
"bgHeaderHasFocus": "#474747",
"bgHeaderHovered": "#404040",
"bgBubble": "#212121",
"bgBubbleSelected": "#000000",
"bgSearchResult": "#423c24",
"borderColor": "rgba(225,225,225,0.2)",
"drilldownBorder": "rgba(225,225,225,0.4)",
"linkColor": "#4F5DFF",
"headerFontStyle": "bold 14px",
"baseFontStyle": "13px",
"fontFamily": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif",
}
```
```python demo
rx.data_editor(
columns=DataTableState2.cols,
data=DataTableState2.data,
on_paste=True,
draw_focus_ring=False,
freeze_columns=2,
group_header_height=50,
header_height=60,
max_column_width=300,
min_column_width=100,
row_height=50,
row_markers='clickable-number',
smooth_scroll_x=True,
vertical_border=False,
column_select="multi",
overscroll_x=100,
theme=darkTheme,
on_cell_clicked=DataTableState2.get_clicked_data,
on_cell_edited=DataTableState2.get_edited_data,
on_group_header_context_menu=DataTableState2.get_group_header_right_click,
on_item_hovered=DataTableState2.get_item_hovered,
on_delete=DataTableState2.get_deleted_item,
on_column_resize=DataTableState2.column_resize,
)
```

View File

@ -0,0 +1,328 @@
import asyncio
from typing import Any
import httpx
import reflex as rx
class DataTableState(rx.State):
"""The app state."""
clicked_cell: str = "Cell clicked: "
edited_cell: str = "Cell edited: "
cols: list[dict] = [
{"title": "Title", "type": "str"},
{
"title": "Name",
"type": "str",
"width": 300,
},
{
"title": "Birth",
"type": "str",
"width": 150,
},
{
"title": "Human",
"type": "bool",
"width": 80,
},
{
"title": "House",
"type": "str",
},
{
"title": "Wand",
"type": "str",
"width": 250,
},
{
"title": "Patronus",
"type": "str",
},
{
"title": "Blood status",
"type": "str",
"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",
],
]
def get_clicked_data(self, pos) -> str:
self.clicked_cell = f"Cell clicked: {pos}"
def get_edited_data(self, pos, val) -> str:
col, row = pos
self.data[row][col] = val["data"]
self.edited_cell = f"Cell edited: {pos}, Cell value: {val['data']}"
class DataTableState2(rx.State):
"""The app state."""
clicked_cell: str = "Cell clicked: "
edited_cell: str = "Cell edited: "
right_clicked_group_header: str = "Group header right clicked: "
item_hovered: str = "Item Hovered: "
deleted: str = "Deleted: "
cols: list[dict] = [
{
"title": "Title",
"type": "str",
"width": 100,
},
{
"title": "Name",
"type": "str",
"group": "Data",
"width": 200,
},
{
"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",
],
]
def get_clicked_data(self, pos) -> str:
self.clicked_cell = f"Cell clicked: {pos}"
def get_edited_data(self, pos, val) -> str:
col, row = pos
self.data[row][col] = val["data"]
self.edited_cell = f"Cell edited: {pos}, Cell value: {val['data']}"
def get_group_header_right_click(self, index, val):
self.right_clicked_group_header = f"Group header right clicked at index: {index}, Group header value: {val['group']}"
def get_item_hovered(self, pos) -> str:
self.item_hovered = (
f"Item Hovered type: {pos['kind']}, Location: {pos['location']}"
)
def get_deleted_item(self, selection):
self.deleted = f"Deleted cell: {selection['current']['cell']}"
def column_resize(self, col, width):
self.cols[col["pos"]]["width"] = width
class DataTableLiveState(rx.State):
"The app state."
running: bool = False
table_data: list[dict[str, Any]] = []
rate: int = 0.4
columns: list[dict[str, str]] = [
{
"title": "id",
"id": "v1",
"type": "int",
"width": 100,
},
{
"title": "advice",
"id": "v2",
"type": "str",
"width": 750,
},
]
@rx.background
async def live_stream(self):
while True:
await asyncio.sleep(1 / self.rate)
if not self.running:
break
async with self:
if len(self.table_data) > 50:
self.table_data.pop(0)
res = httpx.get("https://api.adviceslip.com/advice")
data = res.json()
self.table_data.append(
{"v1": data["slip"]["id"], "v2": data["slip"]["advice"]}
)
def toggle_pause(self):
self.running = not self.running
if self.running:
return DataTableLiveState.live_stream

View File

@ -0,0 +1,108 @@
```python exec
import reflex as rx
from docs.datatable_tutorial.datatable_tutorial_utils import DataTableLiveState
darkTheme = {
"accentColor": "#8c96ff",
"accentLight": "rgba(202, 206, 255, 0.253)",
"textDark": "#ffffff",
"textMedium": "#b8b8b8",
"textLight": "#a0a0a0",
"textBubble": "#ffffff",
"bgIconHeader": "#b8b8b8",
"fgIconHeader": "#000000",
"textHeader": "#a1a1a1",
"textHeaderSelected": "#000000",
"bgCell": "#16161b",
"bgCellMedium": "#202027",
"bgHeader": "#212121",
"bgHeaderHasFocus": "#474747",
"bgHeaderHovered": "#404040",
"bgBubble": "#212121",
"bgBubbleSelected": "#000000",
"bgSearchResult": "#423c24",
"borderColor": "rgba(225,225,225,0.2)",
"drilldownBorder": "rgba(225,225,225,0.4)",
"linkColor": "#4F5DFF",
"headerFontStyle": "bold 14px",
"baseFontStyle": "13px",
"fontFamily": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif",
}
```
# Live Streaming example
Lastly let's add in an API so we can live stream data into our datatable.
Here we use a [Background Task](https://reflex.dev/docs/advanced-guide/background-tasks) to stream the data into the table without blocking UI interactivity. We call an advice API using `httpx` and then append that data to the `self.table_data` state var. We also create a button that allows us to start and pause the streaming of the data by changing the value of the boolean state var `running` using the event handler `toggle_pause`. If the `running` state var is set to `True` we stream the API data, when it is set to `False` we break out of the `while` loop and end the background event.
```python
class DataTableLiveState(BaseState):
"The app state."
running: bool = False
table_data: list[dict[str, Any]] = []
rate: int = 0.4
columns: list[dict[str, str]] = [
{
"title": "id",
"id": "v1",
"type": "int",
"width": 100,
},
{
"title": "advice",
"id": "v2",
"type": "str",
"width": 750,
},
]
@rx.background
async def live_stream(self):
while True:
await asyncio.sleep(1 / self.rate)
if not self.running:
break
async with self:
if len(self.table_data) > 50:
self.table_data.pop(0)
res = httpx.get('https://api.adviceslip.com/advice')
data = res.json()
self.table_data.append(\{"v1": data["slip"]["id"], "v2": data["slip"]["advice"]})
def toggle_pause(self):
self.running = not self.running
if self.running:
return DataTableLiveState.live_stream
```
```python demo
rx.vstack(
rx.stack(
rx.cond(
~DataTableLiveState.running,
rx.button("Start", on_click=DataTableLiveState.toggle_pause, color_scheme='green'),
rx.button("Pause", on_click=DataTableLiveState.toggle_pause, color_scheme='red'),
),
),
rx.data_editor(
columns=DataTableLiveState.columns,
data=DataTableLiveState.table_data,
draw_focus_ring=True,
row_height=50,
smooth_scroll_x=True,
smooth_scroll_y=True,
column_select="single",
# style
theme=darkTheme,
),
overflow_x="auto",
width="100%",
height="30vh",
)
```

View File

@ -0,0 +1,87 @@
```python exec
import reflex as rx
from docs.datatable_tutorial.datatable_tutorial_utils import DataTableState, DataTableState2
from pcweb.pages.docs import library
```
# Data Table (Editable) Tutorial
```md alert info
#There is another [datatable component]({library.datadisplay.datatable.path}), which is only used for displaying data and does not support user interactivity or editing.
```
```python eval
rx.box(height="2em")
```
We need to start by defining our columns that describe the shape of our data. The column var should be typed as a `list` of `dict` (`list[dict]`), where each item describes the attributes of a single column in the table.
Each column dict recognizes the keys below:
1. `title`: The text to display in the header of the column
2. `id`: An id for the column, if not defined, will default to a lower case of title
3. `width`: The width of the column (in pixels)
4. `type`: The type of the columns, default to "str"
Below we define `DataTableState` with columns definitions in the `cols` var, and data about Harry Potter characters in the `data` var..
```python
class DataTableState(rx.State):
"""The app state."""
cols: list[dict] = [
{\"title": "Title", "type": "str"},
{
"title": "Name",
"type": "str",
"width": 300,
},
{
"title": "Birth",
"type": "str",
"width": 150,
},
{
"title": "Human",
"type": "bool",
"width": 80,
},
{
"title": "House",
"type": "str",
},
{
"title": "Wand",
"type": "str",
"width": 250,
},
{
"title": "Patronus",
"type": "str",
},
{
"title": "Blood status",
"type": "str",
"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"],
]
```
We then define a basic table by passing the previously defined state vars as props `columns` and `data` to the `rx.data_editor()` component,
```python demo
rx.data_editor(
columns=DataTableState.cols,
data=DataTableState.data,
)
```
This is enough to display the data, but there is no way to interact with it. On the next page we will explore how to add interactivity to our datatable.

View File

@ -1,261 +0,0 @@
```diff
+ Suchst du nach Pynecone? Dann bist du hier in der richtigen Repository. Pynecone wurde in Reflex umbenannt. +
```
<div align="center">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
<hr>
### **✨ Performante, anpassbare Web-Apps in purem Python. Bereitstellung in Sekunden. ✨**
[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex)
![versions](https://img.shields.io/pypi/pyversions/reflex.svg)
[![Documentation](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction)
[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ)
</div>
---
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
---
# Reflex
Reflex ist eine Bibliothek, mit der man Full-Stack-Web-Applikationen in purem Python erstellen kann.
Wesentliche Merkmale:
* **Pures Python** - Schreibe dein Front- und Backend in Python, es gibt also keinen Grund, JavaScript zu lernen.
* **Volle Flexibilität** - Reflex ist einfach zu handhaben, kann aber auch für komplexe Anwendungen skaliert werden.
* **Sofortige Bereitstellung** - Nach dem Erstellen kannst du deine App mit einem [einzigen Befehl](https://reflex.dev/docs/hosting/deploy-quick-start/) bereitstellen oder auf deinem eigenen Server hosten.
Auf unserer [Architektur-Seite](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) erfahren Sie, wie Reflex unter der Haube funktioniert.
## ⚙️ Installation
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.10+):
```bash
pip install reflex
```
## 🥳 Erstelle deine erste App
Die Installation von `reflex` installiert auch das `reflex`-Kommandozeilen-Tool.
Teste, ob die Installation erfolgreich war, indem du ein neues Projekt erstellst. (Ersetze `my_app_name` durch deinen Projektnamen):
```bash
mkdir my_app_name
cd my_app_name
reflex init
```
Dieser Befehl initialisiert eine Vorlage in deinem neuen Verzeichnis.
Du kannst diese App im Entwicklungsmodus ausführen:
```bash
reflex run
```
Du solltest deine App unter http://localhost:3000 laufen sehen.
Nun kannst du den Quellcode in `my_app_name/my_app_name.py` ändern. Reflex hat schnelle Aktualisierungen, sodass du deine Änderungen sofort siehst, wenn du deinen Code speicherst.
## 🫧 Beispiel-App
Lass uns ein Beispiel durchgehen: die Erstellung einer Benutzeroberfläche für die Bildgenerierung mit [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Zur Vereinfachung rufen wir einfach die [OpenAI-API](https://platform.openai.com/docs/api-reference/authentication) auf, aber du könntest dies auch durch ein lokal ausgeführtes ML-Modell ersetzen.
&nbsp;
<div align="center">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="Eine Benutzeroberfläche für DALL·E, die im Prozess der Bildgenerierung gezeigt wird." width="550" />
</div>
&nbsp;
Hier ist der komplette Code, um dies zu erstellen. Das alles wird in einer Python-Datei gemacht!
```python
import reflex as rx
import openai
openai_client = openai.OpenAI()
class State(rx.State):
"""Der Zustand der App."""
prompt = ""
image_url = ""
processing = False
complete = False
def get_image(self):
"""Hole das Bild aus dem Prompt."""
if self.prompt == "":
return rx.window_alert("Prompt Empty")
self.processing, self.complete = True, False
yield
response = openai_client.images.generate(
prompt=self.prompt, n=1, size="1024x1024"
)
self.image_url = response.data[0].url
self.processing, self.complete = False, True
def index():
return rx.center(
rx.vstack(
rx.heading("DALL-E", font_size="1.5em"),
rx.input(
placeholder="Enter a prompt..",
on_blur=State.set_prompt,
width="25em",
),
rx.button(
"Generate Image",
on_click=State.get_image,
width="25em",
loading=State.processing
),
rx.cond(
State.complete,
rx.image(src=State.image_url, width="20em"),
),
align="center",
),
width="100%",
height="100vh",
)
# Füge Zustand und Seite zur App hinzu.
app = rx.App()
app.add_page(index, title="Reflex:DALL-E")
```
## Schauen wir uns das mal genauer an.
<div align="center">
<img src="docs/images/dalle_colored_code_example.png" alt="Erläuterung der Unterschiede zwischen Backend- und Frontend-Teilen der DALL-E-App." width="900" />
</div>
### **Reflex-UI**
Fangen wir mit der Benutzeroberfläche an.
```python
def index():
return rx.center(
...
)
```
Diese `index`-Funktion definiert das Frontend der App.
Wir verwenden verschiedene Komponenten wie `center`, `vstack`, `input` und `button`, um das Frontend zu erstellen. Komponenten können ineinander verschachtelt werden, um komplexe Layouts zu erstellen. Und du kannst Schlüsselwortargumente verwenden, um sie mit der vollen Kraft von CSS zu stylen.
Reflex wird mit [über 60 eingebauten Komponenten](https://reflex.dev/docs/library) geliefert, die dir den Einstieg erleichtern. Wir fügen aktiv weitere Komponenten hinzu, und es ist einfach, [eigene Komponenten zu erstellen](https://reflex.dev/docs/wrapping-react/overview/).
### **State**
Reflex stellt deine Benutzeroberfläche als Funktion deines Zustands dar.
```python
class State(rx.State):
"""Der Zustand der App."""
prompt = ""
image_url = ""
processing = False
complete = False
```
Der Zustand definiert alle Variablen (genannt Vars) in einer App, die sich ändern können, und die Funktionen, die sie ändern.
Hier besteht der Zustand aus einem `prompt` und einer `image_url`. Es gibt auch die Booleans `processing` und `complete`, um anzuzeigen, wann der Button deaktiviert werden soll (während der Bildgenerierung) und wann das resultierende Bild angezeigt werden soll.
### **Event-Handler**
```python
def get_image(self):
"""Hole das Bild aus dem Prompt."""
if self.prompt == "":
return rx.window_alert("Prompt Empty")
self.processing, self.complete = True, False
yield
response = openai_client.images.generate(
prompt=self.prompt, n=1, size="1024x1024"
)
self.image_url = response.data[0].url
self.processing, self.complete = False, True
```
Innerhalb des Zustands definieren wir Funktionen, die als Event-Handler bezeichnet werden und die Zustand-Variablen ändern. Event-Handler sind die Art und Weise, wie wir den Zustand in Reflex ändern können. Sie können als Reaktion auf Benutzeraktionen aufgerufen werden, z.B. beim Klicken auf eine Schaltfläche oder bei der Eingabe in ein Textfeld. Diese Aktionen werden als Ereignisse bezeichnet.
Unsere DALL-E.-App hat einen Event-Handler, `get_image`, der dieses Bild von der OpenAI-API abruft. Die Verwendung von `yield` in der Mitte eines Event-Handlers führt zu einer Aktualisierung der Benutzeroberfläche. Andernfalls wird die Benutzeroberfläche am Ende des Ereignishandlers aktualisiert.
### **Routing**
Schließlich definieren wir unsere App.
```python
app = rx.App()
```
Wir fügen der Indexkomponente eine Seite aus dem Stammverzeichnis der Anwendung hinzu. Wir fügen auch einen Titel hinzu, der in der Seitenvorschau/Browser-Registerkarte angezeigt wird.
```python
app.add_page(index, title="DALL-E")
```
Du kannst eine mehrseitige App erstellen, indem du weitere Seiten hinzufügst.
## 📑 Ressourcen
<div align="center">
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Komponentenbibliothek](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Galerie](https://reflex.dev/docs/gallery) &nbsp; | &nbsp; 🛸 [Bereitstellung](https://reflex.dev/docs/hosting/deploy-quick-start) &nbsp;
</div>
## ✅ Status
Reflex wurde im Dezember 2022 unter dem Namen Pynecone gestartet.
Ab Februar 2024 befindet sich unser Hosting-Service in der Alpha-Phase! In dieser Zeit kann jeder seine Apps kostenlos bereitstellen. Siehe unsere [Roadmap](https://github.com/reflex-dev/reflex/issues/2727), um zu sehen, was geplant ist.
Reflex hat wöchentliche Veröffentlichungen und neue Features! Stelle sicher, dass du dieses Repository mit einem :star: Stern markierst und :eyes: beobachtest, um auf dem Laufenden zu bleiben.
## Beitragende
Wir begrüßen Beiträge jeder Größe! Hier sind einige gute Möglichkeiten, um in der Reflex-Community zu starten.
- **Tritt unserem Discord bei**: Unser [Discord](https://discord.gg/T5WSbC2YtQ) ist der beste Ort, um Hilfe für dein Reflex-Projekt zu bekommen und zu besprechen, wie du beitragen kannst.
- **GitHub-Diskussionen**: Eine großartige Möglichkeit, über Funktionen zu sprechen, die du hinzugefügt haben möchtest oder Dinge, die verwirrend sind/geklärt werden müssen.
- **GitHub-Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) sind eine ausgezeichnete Möglichkeit, Bugs zu melden. Außerdem kannst du versuchen, ein bestehendes Problem zu lösen und eine PR einzureichen.
Wir suchen aktiv nach Mitwirkenden, unabhängig von deinem Erfahrungslevel oder deiner Erfahrung. Um beizutragen, sieh dir [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) an.
## Vielen Dank an unsere Mitwirkenden:
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
</a>
## Lizenz
Reflex ist Open-Source und lizenziert unter der [Apache License 2.0](LICENSE).

View File

@ -1,246 +0,0 @@
```diff
+ ¿Buscando Pynecone? Estás en el repositorio correcto. Pynecone ha sido renombrado a Reflex. +
```
<div align="center">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
<hr>
### **✨ Aplicaciones web personalizables y eficaces en Python puro. Despliega tu aplicación en segundos. ✨**
[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex)
![Pruebas](https://github.com/pynecone-io/pynecone/actions/workflows/integration.yml/badge.svg)
![Versiones](https://img.shields.io/pypi/pyversions/reflex.svg)
[![Documentación](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction)
[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ)
</div>
---
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
---
# Reflex
Reflex es una biblioteca para construir aplicaciones web full-stack en Python puro.
Características clave:
* **Python puro** - Escribe el frontend y backend de tu aplicación en Python, sin necesidad de aprender JavaScript.
* **Flexibilidad total** - Reflex es fácil para empezar, pero también puede escalar a aplicaciones complejas.
* **Despliegue instantáneo** - Después de construir, despliega tu aplicación con un [solo comando](https://reflex.dev/docs/hosting/deploy-quick-start/) u hospédala en tu propio servidor.
Consulta nuestra [página de arquitectura](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) para aprender cómo funciona Reflex en detalle.
## ⚙️ Instalación
Abra un terminal y ejecute (Requiere Python 3.10+):
```bash
pip install reflex
```
## 🥳 Crea tu primera aplicación
Al instalar `reflex` también se instala la herramienta de línea de comandos `reflex`.
Compruebe que la instalación se ha realizado correctamente creando un nuevo proyecto. (Sustituye `my_app_name` por el nombre de tu proyecto):
```bash
mkdir my_app_name
cd my_app_name
reflex init
```
Este comando inicializa una plantilla en tu nuevo directorio.
Puedes iniciar esta aplicación en modo de desarrollo:
```bash
reflex run
```
Debería ver su aplicación ejecutándose en http://localhost:3000.
Ahora puede modificar el código fuente en `my_app_name/my_app_name.py`. Reflex se actualiza rápidamente para que pueda ver los cambios al instante cuando guarde el código.
## 🫧 Ejemplo de una Aplicación
Veamos un ejemplo: crearemos una UI de generación de imágenes en torno a [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Para simplificar, solo llamamos a la [API de OpenAI](https://platform.openai.com/docs/api-reference/authentication), pero podrías reemplazar esto con un modelo ML ejecutado localmente.
&nbsp;
<div align="center">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="Un envoltorio frontend para DALL·E, mostrado en el proceso de generar una imagen." width="550" />
</div>
&nbsp;
Aquí está el código completo para crear esto. ¡Todo esto se hace en un archivo de Python!
```python
import reflex as rx
import openai
openai_client = openai.OpenAI()
class State(rx.State):
"""El estado de la aplicación"""
prompt = ""
image_url = ""
processing = False
complete = False
def get_image(self):
"""Obtiene la imagen desde la consulta."""
if self.prompt == "":
return rx.window_alert("Prompt Empty")
self.processing, self.complete = True, False
yield
response = openai_client.images.generate(
prompt=self.prompt, n=1, size="1024x1024"
)
self.image_url = response.data[0].url
self.processing, self.complete = False, True
def index():
return rx.center(
rx.vstack(
rx.heading("DALL-E", font_size="1.5em"),
rx.input(
placeholder="Enter a prompt..",
on_blur=State.set_prompt,
width="25em",
),
rx.button(
"Generate Image",
on_click=State.get_image,
width="25em",
loading=State.processing
),
rx.cond(
State.complete,
rx.image(src=State.image_url, width="20em"),
),
align="center",
),
width="100%",
height="100vh",
)
# Agrega el estado y la pagina a la aplicación
app = rx.App()
app.add_page(index, title="Reflex:DALL-E")
```
## Vamos a analizarlo.
<div align="center">
<img src="https://github.com/reflex-dev/reflex/blob/main/docs/images/dalle_colored_code_example.png?raw=true" alt="Explicando las diferencias entre las partes del backend y frontend de la aplicación DALL-E." width="900" />
</div>
### **Reflex UI**
Empezemos por la interfaz de usuario (UI).
```python
def index():
return rx.center(
...
)
```
Esta función `index` define el frontend de la aplicación.
Utilizamos diferentes componentes como `center`, `vstack`, `input`, y `button` para construir el frontend. Los componentes pueden anidarse unos dentro de otros para crear diseños complejos. Además, puedes usar argumentos de tipo keyword para darles estilo con toda la potencia de CSS.
Reflex viene con [mas de 60 componentes incorporados](https://reflex.dev/docs/library) para ayudarle a empezar. Estamos añadiendo activamente más componentes y es fácil [crear sus propios componentes](https://reflex.dev/docs/wrapping-react/overview/).
### **Estado**
Reflex representa su UI como una función de su estado (State).
```python
class State(rx.State):
"""El estado de la aplicación"""
prompt = ""
image_url = ""
processing = False
complete = False
```
El estado (State) define todas las variables (llamadas vars) de una aplicación que pueden cambiar y las funciones que las modifican.
Aquí el estado se compone de `prompt` e `image_url`. También están los booleanos `processing` y `complete` para indicar cuando se deshabilite el botón (durante la generación de la imagen) y cuando se muestre la imagen resultante.
### **Manejadores de Evento**
```python
def get_image(self):
"""Obtiene la imagen desde la consulta."""
if self.prompt == "":
return rx.window_alert("Prompt Empty")
self.processing, self.complete = True, False
yield
response = openai_client.images.generate(
prompt=self.prompt, n=1, size="1024x1024"
)
self.image_url = response.data[0].url
self.processing, self.complete = False, True
```
Dentro del estado, definimos funciones llamadas manejadores de eventos que cambian las variables de estado. Los Manejadores de Evento son la manera que podemos modificar el estado en Reflex. Pueden ser activados en respuesta a las acciones del usuario, como hacer clic en un botón o escribir en un cuadro de texto. Estas acciones se llaman eventos.
Nuestra aplicación DALL·E tiene un manipulador de eventos, `get_image` que recibe esta imagen del OpenAI API. El uso de `yield` en medio de un manipulador de eventos hará que la UI se actualice. De lo contrario, la interfaz se actualizará al final del manejador de eventos.
### **Enrutamiento**
Por último, definimos nuestra app.
```python
app = rx.App()
```
Añadimos una página desde la raíz (root) de la aplicación al componente de índice (index). También agregamos un título que se mostrará en la vista previa de la página/pestaña del navegador.
```python
app.add_page(index, title="DALL-E")
```
Puedes crear una aplicación multipágina añadiendo más páginas.
## 📑 Recursos
<div align="center">
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Librería de componentes](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Galería](https://reflex.dev/docs/gallery) &nbsp; | &nbsp; 🛸 [Despliegue](https://reflex.dev/docs/hosting/deploy-quick-start) &nbsp;
</div>
## ✅ Estado
Reflex se lanzó en diciembre de 2022 con el nombre de Pynecone.
¡Desde febrero de 2024, nuestro servicio de alojamiento está en fase alfa! Durante este tiempo, cualquiera puede implementar sus aplicaciones de forma gratuita. Consulta nuestra [hoja de ruta](https://github.com/reflex-dev/reflex/issues/2727) para ver qué está planeado.
¡Reflex tiene nuevas versiones y características cada semana! Asegúrate de :star: marcar como favorito y :eyes: seguir este repositorio para mantenerte actualizado.
## Contribuciones
¡Aceptamos contribuciones de cualquier tamaño! A continuación encontrará algunas buenas formas de iniciarse en la comunidad Reflex.
- **Únete a nuestro Discord**: Nuestro [Discord](https://discord.gg/T5WSbC2YtQ) es el mejor lugar para obtener ayuda en su proyecto Reflex y discutir cómo puedes contribuir.
- **Discusiones de GitHub**: Una excelente manera de hablar sobre las características que deseas agregar o las cosas que te resultan confusas o necesitan aclaración.
- **GitHub Issues**: Las incidencias son una forma excelente de informar de errores. Además, puedes intentar resolver un problema existente y enviar un PR.
Buscamos colaboradores, sin importar su nivel o experiencia. Para contribuir consulta [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
## Licencia
Reflex es de código abierto y está licenciado bajo la [Apache License 2.0](LICENSE).

View File

@ -0,0 +1,175 @@
```python exec
import reflex as rx
from pcweb import constants, styles
```
# Background Tasks
A background task is a special type of `EventHandler` that may run
concurrently with other `EventHandler` functions. This enables long-running
tasks to execute without blocking UI interactivity.
A background task is defined by decorating an async `State` method with
`@rx.background`.
Whenever a background task needs to interact with the state, **it must enter an
`async with self` context block** which refreshes the state and takes an
exclusive lock to prevent other tasks or event handlers from modifying it
concurrently. Because other `EventHandler` functions may modify state while the
task is running, **outside of the context block, Vars accessed by the background
task may be _stale_**. Attempting to modify the state from a background task
outside of the context block will raise an `ImmutableStateError` exception.
In the following example, the `my_task` event handler is decorated with
`@rx.background` and increments the `counter` variable every half second, as
long as certain conditions are met. While it is running, the UI remains
interactive and continues to process events normally.
```python demo exec
import asyncio
import reflex as rx
class MyTaskState(rx.State):
counter: int = 0
max_counter: int = 10
running: bool = False
_n_tasks: int = 0
@rx.background
async def my_task(self):
async with self:
# The latest state values are always available inside the context
if self._n_tasks > 0:
# only allow 1 concurrent task
return
# State mutation is only allowed inside context block
self._n_tasks += 1
while True:
async with self:
# Check for stopping conditions inside context
if self.counter >= self.max_counter:
self.running = False
if not self.running:
self._n_tasks -= 1
return
self.counter += 1
# Await long operations outside the context to avoid blocking UI
await asyncio.sleep(0.5)
def toggle_running(self):
self.running = not self.running
if self.running:
return MyTaskState.my_task
def clear_counter(self):
self.counter = 0
def background_task_example():
return rx.hstack(
rx.heading(MyTaskState.counter, " /"),
rx.chakra.number_input(
value=MyTaskState.max_counter,
on_change=MyTaskState.set_max_counter,
width="8em",
),
rx.button(
rx.cond(~MyTaskState.running, "Start", "Stop"),
on_click=MyTaskState.toggle_running,
),
rx.button(
"Reset",
on_click=MyTaskState.clear_counter,
),
)
```
## Task Lifecycle
When a background task is triggered, it starts immediately, saving a reference to
the task in `app.background_tasks`. When the task completes, it is removed from
the set.
Multiple instances of the same background task may run concurrently, and the
framework makes no attempt to avoid duplicate tasks from starting.
It is up to the developer to ensure that duplicate tasks are not created under
the circumstances that are undesirable. In the example above, the `_n_tasks`
backend var is used to control whether `my_task` will enter the increment loop,
or exit early.
## Background Task Limitations
Background tasks mostly work like normal `EventHandler` methods, with certain exceptions:
* Background tasks must be `async` functions.
* Background tasks cannot modify the state outside of an `async with self` context block.
* Background tasks may read the state outside of an `async with self` context block, but the value may be stale.
* Background tasks may not be directly called from other event handlers or background tasks. Instead use `yield` or `return` to trigger the background task.
## Low-level API
The `@rx.background` decorator is a convenience wrapper around the lower-level
`App.modify_state` async contextmanager. If more control over task lifecycle is
needed, arbitrary async tasks may safely manipulate the state using an
`async with app.modify_state(token) as state` context block. In this case the
`token` for a state is retrieved from `state.get_token()` and identifies a
single instance of the state (i.e. the state for an individual browser tab).
Care must be taken to **never directly modify the state outside of the
`modify_state` contextmanager**. If the code that creates the task passes a
direct reference to the state instance, this can introduce subtle bugs or not
work at all (if redis is used for state storage).
The following example creates an arbitrary `asyncio.Task` to fetch data and then
uses the low-level API to safely update the state and send the changes to the
frontend.
```python demo exec
import asyncio
import httpx
import reflex as rx
my_tasks = set()
async def _fetch_data(app, token):
async with httpx.AsyncClient() as client:
response = await client.get("https://api.github.com/zen")
async with app.modify_state(token) as state:
substate = state.get_substate(
LowLevelState.get_full_name().split("."),
)
substate.result = response.text
class LowLevelState(rx.State):
result: str = ""
def fetch_data(self):
task = asyncio.create_task(
_fetch_data(
app=rx.utils.prerequisites.get_app().app,
token=self.get_token(),
),
)
# Always save a reference to your tasks until they are done
my_tasks.add(task)
task.add_done_callback(my_tasks.discard)
def low_level_example():
return rx.vstack(
rx.text(LowLevelState.result),
rx.button(
"Fetch Data",
on_click=LowLevelState.fetch_data,
),
)
```

View File

@ -0,0 +1,90 @@
```python exec
import reflex as rx
```
# Chaining events
## Calling Event Handlers From Event Handlers
You can call other event handlers from event handlers to keep your code modular. Just use the `self.call_handler` syntax to run another event handler. As always, you can yield within your function to send incremental updates to the frontend.
```python demo exec
import asyncio
class CallHandlerState(rx.State):
count: int = 0
progress: int = 0
async def run(self):
# Reset the count.
self.set_progress(0)
yield
# Count to 10 while showing progress.
for i in range(10):
# Wait and increment.
await asyncio.sleep(0.5)
self.count += 1
# Update the progress.
self.set_progress(i + 1)
# Yield to send the update.
yield
def call_handler_example():
return rx.vstack(
rx.badge(CallHandlerState.count, font_size="1.5em", color_scheme="green"),
rx.progress(value=CallHandlerState.progress, max=10, width="100%"),
rx.button("Run", on_click=CallHandlerState.run),
)
```
## Returning Events From Event Handlers
So far, we have only seen events that are triggered by components. However, an event handler can also return events.
In Reflex, event handlers run synchronously, so only one event handler can run at a time, and the events in the queue will be blocked until the current event handler finishes.The difference between returning an event and calling an event handler is that returning an event will send the event to the frontend and unblock the queue.
```md alert
Be sure to use the class name `State` (or any substate) rather than `self` when returning events.
```
Try entering an integer in the input below then clicking out.
```python demo exec
class CollatzState(rx.State):
count: int = 0
def start_collatz(self, count: str):
"""Run the collatz conjecture on the given number."""
self.count = abs(int(count))
return CollatzState.run_step
async def run_step(self):
"""Run a single step of the collatz conjecture."""
while self.count > 1:
await asyncio.sleep(0.5)
if self.count % 2 == 0:
# If the number is even, divide by 2.
self.count /= 2
else:
# If the number is odd, multiply by 3 and add 1.
self.count = self.count * 3 + 1
yield
def collatz_example():
return rx.vstack(
rx.badge(CollatzState.count, font_size="1.5em", color_scheme="green"),
rx.chakra.input(on_blur=CollatzState.start_collatz),
)
```
In this example, we run the [Collatz Conjecture](https://en.wikipedia.org/wiki/Collatz_conjecture) on a number entered by the user.
When the `on_blur` event is triggered, the event handler `start_collatz` is called. It sets the initial count, then calls `run_step` which runs until the count reaches `1`.

View File

@ -0,0 +1,34 @@
```python exec
import reflex as rx
```
# Event Arguments
In some use cases, you want to pass additional arguments to your event handlers. To do this you can bind an event trigger to a lambda, which can call your event handler with the arguments you want.
Try typing a color in an input below and clicking away from it to change the color of the input.
```python demo exec
class ArgState(rx.State):
colors: list[str] = ["rgba(222,44,12)", "white", "#007ac2"]
def change_color(self, color: str, index: int):
self.colors[index] = color
def event_arguments_example():
return rx.hstack(
rx.chakra.input(default_value=ArgState.colors[0], on_blur=lambda c: ArgState.change_color(c, 0), bg=ArgState.colors[0]),
rx.chakra.input(default_value=ArgState.colors[1], on_blur=lambda c: ArgState.change_color(c, 1), bg=ArgState.colors[1]),
rx.chakra.input(default_value=ArgState.colors[2], on_blur=lambda c: ArgState.change_color(c, 2), bg=ArgState.colors[2]),
)
```
In this case, in we want to pass two arguments to the event handler `change_color`, the color and the index of the color to change.
The `on_blur` event trigger passes the text of the input as an argument to the lambda, and the lambda calls the `change_color` event handler with the text and the index of the input.
```md alert warning
# Event Handler Parameters should provide type annotations.
Like state vars, be sure to provide the right type annotations for the parameters in an event handler.
```

View File

@ -0,0 +1,41 @@
```python exec
import reflex as rx
from pcweb.pages.docs.library import library
```
# Events Overview
Events are how we modify the state and make the app interactive.
Event triggers are component props that create an event to be sent to an event handler.
Each component supports a set of events triggers. They are described in each [component's documentation]({library.path}) in the event trigger section.
Lets take a look at an example below. Try mousing over the heading to change the word.
```python demo exec
class WordCycleState(rx.State):
# The words to cycle through.
text: list[str] = ["Welcome", "to", "Reflex", "!"]
# The index of the current word.
index: int = 0
def next_word(self):
self.index = (self.index + 1) % len(self.text)
@rx.var
def get_text(self) -> str:
return self.text[self.index]
def event_triggers_example():
return rx.heading(
WordCycleState.get_text,
on_mouse_over=WordCycleState.next_word,
color="green",
)
```
In this example, the heading component has the event trigger, `on_mouse_over`.
Whenever the user hovers over the heading, the `next_word` handler will be called to cycle the word. Once the handler returns, the UI will be updated to reflect the new state.

View File

@ -0,0 +1,21 @@
```python exec
import reflex as rx
```
# Page Load Events
You can also specify a function to run when the page loads. This can be useful for fetching data once vs on every render or state change.
In this example, we fetch data when the page loads:
```python
class State(rx.State):
data: Dict[str, Any]
def get_data(self):
# Fetch data
self.data = fetch_data()
@rx.page(on_load=State.get_data)
def index():
return rx.text('A Beautiful App')
```

52
docs/events/setters.md Normal file
View File

@ -0,0 +1,52 @@
```python exec
import reflex as rx
```
# Setters
Every base var has a built-in event handler to set it's value for convenience, called `set_VARNAME`.
Say you wanted to change the value of the select component. You could write your own event handler to do this:
```python demo exec
options: list[str] = ["1", "2", "3", "4"]
class SetterState1(rx.State):
selected: str = "1"
def change(self, value):
self.selected = value
def code_setter():
return rx.vstack(
rx.badge(SetterState1.selected, color_scheme="green"),
rx.select(
options,
on_change= lambda value: SetterState1.change(value),
)
)
```
Or you could could use a built-in setter for conciseness.
```python demo exec
options: list[str] = ["1", "2", "3", "4"]
class SetterState2(rx.State):
selected: str = "1"
def code_setter_2():
return rx.vstack(
rx.badge(SetterState2.selected, color_scheme="green"),
rx.select(
options,
on_change= SetterState2.set_selected,
)
)
```
In this example, the setter for `selected` is `set_selected`. Both of these examples are equivalent.
Setters are a great way to make your code more concise. But if you want to do something more complicated, you can always write your own function in the state.

View File

@ -0,0 +1,20 @@
```python exec
import reflex as rx
from pcweb.pages.docs import api_reference
```
# Special Events
Reflex also has built-in special events can be found in the [reference]({api_reference.special_events.path}).
For example, an event handler can trigger an alert on the browser.
```python demo exec
class SpecialEventsState(rx.State):
def alert(self):
return rx.window_alert("Hello World!")
def special_events_example():
return rx.button("Alert", on_click=SpecialEventsState.alert)
```

View File

@ -0,0 +1,63 @@
```python exec
import reflex as rx
```
# Yielding Multiple Updates
A regular event handler will send a `StateUpdate` when it has finished running. This works fine for basic event, but sometimes we need more complex logic. To update the UI multiple times in an event handler, we can `yield` when we want to send an update.
To do so, we can use the Python keyword `yield`. For every yield inside the function, a `StateUpdate` will be sent to the frontend with the changes up to this point in the execution of the event handler.
```python demo exec
import asyncio
class MultiUpdateState(rx.State):
count: int = 0
async def timed_update(self):
for i in range(5):
await asyncio.sleep(0.5)
self.count += 1
yield
def multi_update():
return rx.vstack(
rx.text(MultiUpdateState.count),
rx.button("Start", on_click=MultiUpdateState.timed_update)
)
```
Here is another example of yielding multiple updates with a loading icon.
```python demo exec
import asyncio
class ProgressExampleState(rx.State):
count: int = 0
show_progress: bool = False
async def increment(self):
self.show_progress = True
yield
# Think really hard.
await asyncio.sleep(0.5)
self.count += 1
self.show_progress = False
def progress_example():
return rx.cond(
ProgressExampleState.show_progress,
rx.chakra.circular_progress(is_indeterminate=True),
rx.heading(
ProgressExampleState.count,
on_click=ProgressExampleState.increment,
_hover={"cursor": "pointer"},
)
)
```

View File

@ -0,0 +1,91 @@
```python exec
config_api_ref_url = "/docs/api-reference/config"
cli_api_ref_url = "/docs/api-reference/cli"
```
# Configuration
Reflex apps can be configured using a configuration file, environment variables, and command line arguments.
## Configuration File
Running `reflex init` will create an `rxconfig.py` file in your root directory.
You can pass keyword arguments to the `Config` class to configure your app.
For example:
```python
# rxconfig.py
import reflex as rx
config = rx.Config(
app_name="my_app_name",
# Connect to your own database.
db_url="postgresql://user:password@localhost:5432/my_db",
# Change the frontend port.
frontend_port=3001,
)
```
See the [config reference]({config_api_ref_url}) for all the parameters available.
## Environment Variables
You can override the configuration file by setting environment variables.
For example, to override the `frontend_port` setting, you can set the `FRONTEND_PORT` environment variable.
```bash
FRONTEND_PORT=3001 reflex run
```
## Command Line Arguments
Finally, you can override the configuration file and environment variables by passing command line arguments to `reflex run`.
```bash
reflex run --frontend-port 3001
```
See the [CLI reference]({cli_api_ref_url}) for all the arguments available.
## Anonymous Usage Statistics
Reflex collects completely anonymous telemetry data about general usage.
Participation in this anonymous program is optional, and you may opt-out if you'd not like to share any information.
### What's Being Collected
Telemetry allows us to understand how Reflex is used, what features are most important, and how we can improve the product.
The following information is collected:
* Operating system
* CPU count
* Memoryd
* Python version
* Reflex version
### How to Opt-Out
To disable telemetry, set `telemetry_enabled=False` in your `rxconfig.py` file.
```python
config = rx.Config(
app_name="hello",
telemetry_enabled=False,
)
```
Alternatively, you can set the `TELEMETRY_ENABLED` environment variable to `False`.
## Customizable App Data Directory
The `REFLEX_DIR` environment variable can be set, which allows users to set the location where Reflex writes helper tools like Bun and NodeJS.
By default we use Platform specific directories:
On windows, `C:/Users/<username>/AppData/Local/reflex` is used.
On macOS, `~/Library/Application Support/reflex` is used.
On linux, `~/.local/share/reflex` is used.

View File

@ -0,0 +1,147 @@
```python exec
from pcweb import constants
import reflex as rx
app_name = "my_app_name"
default_url = "http://localhost:3000"
```
# Installation
Reflex requires Python 3.8+.
## Virtual Environment
We **highly recommend** creating a virtual environment for your project.
[venv]({constants.VENV_URL}) is the standard option. [conda]({constants.CONDA_URL}) and [poetry]({constants.POETRY_URL}) are some alternatives.
## Install on macOS/Linux
We will go with [venv]({constants.VENV_URL}) here.
### Prerequisites
macOS (Apple Silicon) users should install [Rosetta 2](https://support.apple.com/en-us/HT211861). Run this command:
`/usr/sbin/softwareupdate --install-rosetta --agree-to-license`
### Create the project directory
Replace `{app_name}` with your project name. Switch to the new directory.
```bash
mkdir {app_name}
cd {app_name}
```
### Setup virtual environment
```bash
python3 -m venv .venv
source .venv/bin/activate
```
```md alert warning
# Error `No module named venv`
While Python typically ships with `venv` it is not installed by default on some systems.
If so, please install it manually. E.g. on Ubuntu Linux, run `sudo apt-get install python3-venv`.
```
### Install Reflex package
Reflex is available as a [pip](constants.PIP_URL) package.
```bash
pip install reflex
```
```md alert warning
# Error `command not found: pip`
While Python typically ships with `pip` as the standard package management tool, it is not installed by default on some systems.
You may need to install it manually. E.g. on Ubuntu Linux, run `apt-get install python3-pip`
```
### Initialize the project
```bash
reflex init
```
```md alert warning
# Error `command not found: reflex`
If you install Reflex with no virtual environment and get this error it means your `PATH` cannot find the reflex package.
A virtual environment should solve this problem, or you can try running `python3 -m` before the reflex command.
```
## Install on Windows
### Prerequisites
For Windows users, we recommend using [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/about) for optimal performance.
WSL users should refer to instructions for Linux above.
For the rest of this section we will work with native Windows (non-WSL).
We will go with [venv]({constants.VENV_URL}) here, for virtual environments.
### Create a new project directory
```bash
mkdir {app_name}
cd {app_name}
```
### Setup virtual environment
```bash
py -3 -m venv .venv
.venv\\Scripts\\activate
```
### Install Reflex package
```bash
pip install reflex
```
### Initialize the project
```bash
reflex init
```
```md alert warning
# Error `command not found: reflex`
The Reflex framework includes the `reflex` command line (CLI) tool. Using a virtual environment is highly recommended for a seamless experience (see below).",
```
## Run the App
Run it in development mode:
```bash
reflex run
```
Your app runs at [http://localhost:3000](http://localhost:3000).
Reflex prints logs to the terminal. To increase log verbosity to help with debugging, use the `--loglevel` flag:
```bash
reflex run --loglevel debug
```
Reflex will *hot reload* any code changes in real time when running in development mode. Your code edits will show up on [http://localhost:3000](http://localhost:3000) automatically.
## (Optional) Run the demo app
The demo app showcases some of Reflex's features.
```bash
reflex demo
```

View File

@ -0,0 +1,226 @@
```python exec
import reflex as rx
from pcweb import constants, styles
from pcweb.templates.docpage import doccode
from pcweb.pages.docs import tutorial
from pcweb.pages.docs import getting_started
from pcweb.pages.docs import wrapping_react
from pcweb.pages.docs.library import library
from pcweb.pages.docs import vars
```
<!-- TODO how do we consistently rename page title? -->
# Introduction
**Reflex** is an open-source framework for quickly building beautiful, interactive web applications in **pure Python**.
## Goals
```md section
# Pure Python
Use Python for everything. Don't worry about learning a new language.
# Easy to Learn
Build and share your first app in minutes. No web development experience required.
# Full Flexibility
Remain as flexible as traditional web frameworks. Reflex is easy to use, yet allows for advanced use cases.
Build anything from small data science apps to large, multi-page websites. **This entire site was built and deployed with Reflex!**
# Batteries Included
No need to reach for a bunch of different tools. Reflex handles the user interface, server-side logic, and deployment of your app.
```
## An example: Make it count
Here, we go over a simple counter app that lets the user count up or down.
<!-- TODO use radix components, to allow more concise styling - e.g. all them props -->
```python exec
class CounterExampleState(rx.State):
count: int = 0
def increment(self):
self.count += 1
def decrement(self):
self.count -= 1
```
```python demo box
rx.hstack(
rx.button(
"Decrement",
color_scheme="ruby",
on_click=CounterExampleState.decrement,
),
rx.heading(CounterExampleState.count, font_size="2em"),
rx.button(
"Increment",
color_scheme="grass",
on_click=CounterExampleState.increment,
),
spacing="4",
)
```
Here is the full code for this example:
```python
import reflex as rx
class State(rx.State):
count: int = 0
def increment(self):
self.count += 1
def decrement(self):
self.count -= 1
def index():
return rx.hstack(
rx.button(
"Decrement",
color_scheme="ruby",
on_click=State.decrement,
),
rx.heading(State.count, font_size="2em"),
rx.button(
"Increment",
color_scheme="grass",
on_click=State.increment,
),
spacing="4",
)
app = rx.App()
app.add_page(index)
```
## The Structure of a Reflex App
Let's break this example down.
### Import
```python
import reflex as rx
```
We begin by importing the `reflex` package (aliased to `rx`). We reference Reflex objects as `rx.*` by convention.
### State
```python
class State(rx.State):
count: int = 0
```
The state defines all the variables (called **[vars]({vars.base_vars.path})**) in an app that can change, as well as the functions (called **[event_handlers](#event-handlers)**) that change them.
Here our state has a single var, `count`, which holds the current value of the counter. We initialize it to `0`.
### Event Handlers
```python
def increment(self):
self.count += 1
def decrement(self):
self.count -= 1
```
Within the state, we define functions, called **event handlers**, that change the state vars.
Event handlers are the only way that we can modify the state in Reflex.
They can be called in response to user actions, such as clicking a button or typing in a text box.
These actions are called **events**.
Our counter app has two event handlers, `increment` and `decrement`.
### User Interface (UI)
```python
def index():
return rx.hstack(
rx.button(
"Decrement",
color_scheme="ruby",
on_click=State.decrement,
),
rx.heading(State.count, font_size="2em"),
rx.button(
"Increment",
color_scheme="grass",
on_click=State.increment,
),
spacing="4",
)
```
This function defines the app's user interface.
We use different components such as `rx.hstack`, `rx.button`, and `rx.heading` to build the frontend. Components can be nested to create complex layouts, and can be styled using the full power of CSS.
Reflex comes with [50+ built-in components]({library.path}) to help you get started.
We are actively adding more components. Also, it's easy to [wrap your own React components]({wrapping_react.overview.path}).
```python
rx.heading(State.count, font_size="2em"),
```
Components can reference the app's state vars.
The `rx.heading` component displays the current value of the counter by referencing `State.count`.
All components that reference state will reactively update whenever the state changes.
```python
rx.button(
"Decrement",
color_scheme="ruby",
on_click=State.decrement,
),
```
Components interact with the state by binding events triggers to event handlers.
For example, `on_click` is an event that is triggered when a user clicks a component.
The first button in our app binds its `on_click` event to the `State.decrement` event handler. Similarly the second button binds `on_click` to `State.increment`.
In other words, the sequence goes like this:
* User clicks "increment" on the UI.
* `on_click` event is triggered.
* Event handler `State.increment` is called.
* `State.count` is incremented.
* UI updates to reflect the new value of `State.count`.
### Add pages
Next we define our app and add the counter component to the base route.
```python
app = rx.App()
app.add_page(index)
```
## Next Steps
🎉 And that's it!
We've created a simple, yet fully interactive web app in pure Python.
By continuing with our documentation, you will learn how to building awesome apps with Reflex.
For a glimpse of the possibilities, check out these resources:
* For a more real-world example, check out the [tutorial]({tutorial.intro.path}).

View File

@ -0,0 +1,66 @@
# Project Structure
## Directory Structure
```python exec
app_name = "hello"
```
Let's create a new app called `{app_name}`
```bash
mkdir {app_name}
cd {app_name}
reflex init
```
This will create a directory structure like this:
```bash
{app_name}
├── .web
├── assets
├── {app_name}
│ ├── __init__.py
│ └── {app_name}.py
└── rxconfig.py
```
Let's go over each of these directories and files.
## .web
This is where the compiled Javascript files will be stored. You will never need to touch this directory, but it can be useful for debugging.
Each Reflex page will compile to a corresponding `.js` file in the `.web/pages` directory.
## Assets
The `assets` directory is where you can store any static assets you want to be publicly available. This includes images, fonts, and other files.
For example, if you save an image to `assets/image.png` you can display it from your app like this:
```python
rx.image(src="image.png")
```
## Main Project
Initializing your project creates a directory with the same name as your app. This is where you will write your app's logic.
Reflex generates a default app within the `{app_name}/{app_name}.py` file. You can modify this file to customize your app.
## Configuration
The `rxconfig.py` file can be used to configure your app. By default it looks something like this:
```python
import reflex as rx
config = rx.Config(
app_name="{app_name}",
)
```
We will discuss configuration in more detail in the next section.

Some files were not shown because too many files have changed in this diff Show More