Compare commits
2 Commits
main
...
lendemor/m
Author | SHA1 | Date | |
---|---|---|---|
![]() |
dbd074c33d | ||
![]() |
334db18bfa |
11
.coveragerc
11
.coveragerc
@ -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
1
.github/CODEOWNERS
vendored
@ -1 +0,0 @@
|
||||
@reflex-dev/reflex-team
|
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -2,6 +2,7 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
@ -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.
|
19
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
19
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
@ -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.
|
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -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.
|
9
.github/actions/setup_build_env/action.yml
vendored
9
.github/actions/setup_build_env/action.yml
vendored
@ -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
|
||||
|
2
.github/codeql-config.yml
vendored
2
.github/codeql-config.yml
vendored
@ -1,2 +0,0 @@
|
||||
paths-ignore:
|
||||
- "**/tests/**"
|
106
.github/workflows/benchmarks.yml
vendored
106
.github/workflows/benchmarks.yml
vendored
@ -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 }}
|
||||
|
20
.github/workflows/check_generated_pyi.yml
vendored
20
.github/workflows/check_generated_pyi.yml
vendored
@ -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!"
|
||||
|
40
.github/workflows/check_node_latest.yml
vendored
40
.github/workflows/check_node_latest.yml
vendored
@ -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}}
|
@ -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
|
103
.github/workflows/codeql.yml
vendored
103
.github/workflows/codeql.yml
vendored
@ -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}}"
|
17
.github/workflows/dependency-review.yml
vendored
17
.github/workflows/dependency-review.yml
vendored
@ -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'
|
46
.github/workflows/integration_app_harness.yml
vendored
46
.github/workflows/integration_app_harness.yml
vendored
@ -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
|
||||
|
146
.github/workflows/integration_tests.yml
vendored
146
.github/workflows/integration_tests.yml
vendored
@ -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
|
||||
|
18
.github/workflows/integration_tests_wsl.yml
vendored
18
.github/workflows/integration_tests_wsl.yml
vendored
@ -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
|
||||
|
34
.github/workflows/performance.yml
vendored
34
.github/workflows/performance.yml
vendored
@ -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
|
12
.github/workflows/pre-commit.yml
vendored
12
.github/workflows/pre-commit.yml
vendored
@ -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
|
||||
|
24
.github/workflows/reflex_init_in_docker_test.yml
vendored
24
.github/workflows/reflex_init_in_docker_test.yml
vendored
@ -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
|
||||
|
94
.github/workflows/unit_tests.yml
vendored
94
.github/workflows/unit_tests.yml
vendored
@ -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
5
.gitignore
vendored
@ -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
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
```
|
67
README.md
67
README.md
@ -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. ✨**
|
||||
[](https://badge.fury.io/py/reflex)
|
||||

|
||||

|
||||
[](https://reflex.dev/docs/getting-started/introduction)
|
||||
[](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
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
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) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Templates](https://reflex.dev/templates/) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)
|
||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Gallery](https://reflex.dev/docs/gallery) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy)
|
||||
|
||||
</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:
|
||||
|
@ -1,3 +0,0 @@
|
||||
"""Reflex benchmarks."""
|
||||
|
||||
WINDOWS_SKIP_REASON = "Takes too much time as a result of npm"
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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
|
@ -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}"
|
||||
)
|
2
docker-example/.dockerignore
Normal file
2
docker-example/.dockerignore
Normal file
@ -0,0 +1,2 @@
|
||||
.web
|
||||
__pycache__/*
|
@ -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
39
docker-example/Dockerfile
Normal 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
|
@ -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.
|
||||
|
21
docker-example/compose.yaml
Normal file
21
docker-example/compose.yaml
Normal 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
|
@ -1,5 +0,0 @@
|
||||
.web
|
||||
.git
|
||||
__pycache__/*
|
||||
Dockerfile
|
||||
uploaded_files
|
@ -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}
|
@ -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
|
@ -1,8 +0,0 @@
|
||||
.web
|
||||
.git
|
||||
__pycache__/*
|
||||
Dockerfile
|
||||
Caddy.Dockerfile
|
||||
compose.yaml
|
||||
compose.*.yaml
|
||||
uploaded_files
|
@ -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
|
@ -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
|
||||
```
|
@ -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:
|
@ -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:
|
@ -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:
|
@ -1,3 +0,0 @@
|
||||
.web
|
||||
!.web/bun.lockb
|
||||
!.web/package.json
|
@ -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
|
||||
}
|
@ -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
|
@ -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.
|
1
docker-example/requirements.txt
Normal file
1
docker-example/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
reflex
|
@ -1,5 +0,0 @@
|
||||
.web
|
||||
.git
|
||||
__pycache__/*
|
||||
Dockerfile
|
||||
uploaded_files
|
@ -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
|
||||
}
|
@ -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
|
@ -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.
|
@ -1,5 +0,0 @@
|
||||
.web
|
||||
.git
|
||||
__pycache__/*
|
||||
Dockerfile
|
||||
uploaded_files
|
@ -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
|
@ -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
|
||||
```
|
214
docs/api-reference/browser_javascript.md
Normal file
214
docs/api-reference/browser_javascript.md
Normal 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.
|
||||
```
|
191
docs/api-reference/browser_storage.md
Normal file
191
docs/api-reference/browser_storage.md
Normal 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
90
docs/api-reference/cli.md
Normal 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.
|
263
docs/api-reference/event_triggers.md
Normal file
263
docs/api-reference/event_triggers.md
Normal 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, it’s 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, it’s 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, it’s 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, it’s 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, it’s 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, it’s 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, it’s 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, it’s 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 user’s mouse enters an element. For example, it’s called when the user’s 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 user’s mouse leaves an element. For example, it’s called when the user’s 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, it’s 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 user’s mouse leaves an element. For example, it’s called when the user’s 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 user’s mouse enters an element. For example, it’s called when the user’s 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, it’s 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(),
|
||||
)
|
||||
```
|
93
docs/api-reference/special_events.md
Normal file
93
docs/api-reference/special_events.md
Normal 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"))
|
||||
```
|
43
docs/api-routes/overview.md
Normal file
43
docs/api-routes/overview.md
Normal 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()`.
|
42
docs/assets/referencing_assets.md
Normal file
42
docs/assets/referencing_assets.md
Normal 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.
|
89
docs/assets/upload_and_download_files.md
Normal file
89
docs/assets/upload_and_download_files.md
Normal 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}).
|
34
docs/client_storage/overview.md
Normal file
34
docs/client_storage/overview.md
Normal 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)),
|
||||
)
|
||||
```
|
25
docs/components/conditional_props.md
Normal file
25
docs/components/conditional_props.md
Normal 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%",
|
||||
)
|
||||
```
|
269
docs/components/conditional_rendering.md
Normal file
269
docs/components/conditional_rendering.md
Normal 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
81
docs/components/props.md
Normal 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.
|
197
docs/components/rendering_iterables.md
Normal file
197
docs/components/rendering_iterables.md
Normal 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)),
|
||||
)
|
||||
|
||||
```
|
25
docs/components/style_props.md
Normal file
25
docs/components/style_props.md
Normal 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
75
docs/database/overview.md
Normal 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
186
docs/database/queries.md
Normal 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()]
|
||||
```
|
162
docs/database/relationships.md
Normal file
162
docs/database/relationships.md
Normal 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
70
docs/database/tables.md
Normal 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
|
||||
```
|
223
docs/datatable_tutorial/add_interactivity.md
Normal file
223
docs/datatable_tutorial/add_interactivity.md
Normal 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,
|
||||
)
|
||||
```
|
141
docs/datatable_tutorial/add_styling.md
Normal file
141
docs/datatable_tutorial/add_styling.md
Normal 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,
|
||||
)
|
||||
```
|
328
docs/datatable_tutorial/datatable_tutorial_utils.py
Normal file
328
docs/datatable_tutorial/datatable_tutorial_utils.py
Normal 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
|
108
docs/datatable_tutorial/live_stream.md
Normal file
108
docs/datatable_tutorial/live_stream.md
Normal 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",
|
||||
)
|
||||
```
|
87
docs/datatable_tutorial/simple_table.md
Normal file
87
docs/datatable_tutorial/simple_table.md
Normal 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.
|
@ -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. ✨**
|
||||
[](https://badge.fury.io/py/reflex)
|
||||

|
||||
[](https://reflex.dev/docs/getting-started/introduction)
|
||||
[](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.
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
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) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Komponentenbibliothek](https://reflex.dev/docs/library) | 🖼️ [Galerie](https://reflex.dev/docs/gallery) | 🛸 [Bereitstellung](https://reflex.dev/docs/hosting/deploy-quick-start)
|
||||
|
||||
</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).
|
@ -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. ✨**
|
||||
[](https://badge.fury.io/py/reflex)
|
||||

|
||||

|
||||
[](https://reflex.dev/docs/getting-started/introduction)
|
||||
[](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.
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
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) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Librería de componentes](https://reflex.dev/docs/library) | 🖼️ [Galería](https://reflex.dev/docs/gallery) | 🛸 [Despliegue](https://reflex.dev/docs/hosting/deploy-quick-start)
|
||||
|
||||
</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).
|
175
docs/events/background_events.md
Normal file
175
docs/events/background_events.md
Normal 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,
|
||||
),
|
||||
)
|
||||
```
|
90
docs/events/chaining_events.md
Normal file
90
docs/events/chaining_events.md
Normal 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`.
|
34
docs/events/event_arguments.md
Normal file
34
docs/events/event_arguments.md
Normal 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.
|
||||
```
|
41
docs/events/events_overview.md
Normal file
41
docs/events/events_overview.md
Normal 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.
|
21
docs/events/page_load_events.md
Normal file
21
docs/events/page_load_events.md
Normal 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
52
docs/events/setters.md
Normal 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.
|
20
docs/events/special_events.md
Normal file
20
docs/events/special_events.md
Normal 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)
|
||||
```
|
63
docs/events/yield_events.md
Normal file
63
docs/events/yield_events.md
Normal 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"},
|
||||
)
|
||||
)
|
||||
|
||||
```
|
91
docs/getting-started/configuration.md
Normal file
91
docs/getting-started/configuration.md
Normal 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.
|
147
docs/getting-started/installation.md
Normal file
147
docs/getting-started/installation.md
Normal 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
|
||||
```
|
226
docs/getting-started/introduction.md
Normal file
226
docs/getting-started/introduction.md
Normal 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}).
|
66
docs/getting-started/project-structure.md
Normal file
66
docs/getting-started/project-structure.md
Normal 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
Loading…
Reference in New Issue
Block a user