Merge branch 'main' into disable-auto-setters
This commit is contained in:
commit
a0c33c0291
2
.github/actions/setup_build_env/action.yml
vendored
2
.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 ot be invoked as `poetry`.
|
||||
# - Poetry of version `poetry-version` is ready to 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'
|
||||
|
52
.github/workflows/benchmarks.yml
vendored
52
.github/workflows/benchmarks.yml
vendored
@ -5,7 +5,7 @@ on:
|
||||
types:
|
||||
- closed
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- "**/*.md"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@ -15,21 +15,21 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
PYTHONIOENCODING: 'utf8'
|
||||
PYTHONIOENCODING: "utf8"
|
||||
TELEMETRY_ENABLED: false
|
||||
NODE_OPTIONS: '--max_old_space_size=8192'
|
||||
NODE_OPTIONS: "--max_old_space_size=8192"
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
|
||||
jobs:
|
||||
reflex-web:
|
||||
# if: github.event.pull_request.merged == true
|
||||
# if: github.event.pull_request.merged == true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Show OS combos first in GUI
|
||||
os: [ubuntu-latest]
|
||||
python-version: ['3.11.4']
|
||||
node-version: ['18.x']
|
||||
python-version: ["3.12.8"]
|
||||
node-version: ["18.x"]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@ -80,25 +80,23 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Show OS combos first in GUI
|
||||
os: [ubuntu-latest, windows-latest, macos-12]
|
||||
python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0']
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
python-version: ["3.10.16", "3.11.11", "3.12.8"]
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
python-version: '3.10.13'
|
||||
python-version: "3.10.16"
|
||||
- os: windows-latest
|
||||
python-version: '3.9.18'
|
||||
python-version: "3.11.11"
|
||||
# keep only one python version for MacOS
|
||||
- os: macos-latest
|
||||
python-version: '3.9.18'
|
||||
python-version: "3.10.16"
|
||||
- os: macos-latest
|
||||
python-version: '3.10.13'
|
||||
- os: macos-12
|
||||
python-version: '3.12.0'
|
||||
python-version: "3.11.11"
|
||||
include:
|
||||
- os: windows-latest
|
||||
python-version: '3.10.11'
|
||||
python-version: "3.10.11"
|
||||
- os: windows-latest
|
||||
python-version: '3.9.13'
|
||||
python-version: "3.11.9"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@ -123,7 +121,7 @@ jobs:
|
||||
--event-type "${{ github.event_name }}" --pr-id "${{ github.event.pull_request.id }}"
|
||||
|
||||
reflex-dist-size: # This job is used to calculate the size of the Reflex distribution (wheel file)
|
||||
if: github.event.pull_request.merged == true
|
||||
if: github.event.pull_request.merged == true
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
# Prioritize getting more information out of the workflow (even if something fails)
|
||||
@ -133,7 +131,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup_build_env
|
||||
with:
|
||||
python-version: 3.11.5
|
||||
python-version: 3.12.8
|
||||
run-poetry-install: true
|
||||
create-venv-at-path: .venv
|
||||
- name: Build reflex
|
||||
@ -143,25 +141,29 @@ jobs:
|
||||
# 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.11.5 --commit-sha "${{ github.sha }}" --pr-id "${{ github.event.pull_request.id }}"
|
||||
--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
|
||||
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-12]
|
||||
python-version: ['3.11.5']
|
||||
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:
|
||||
@ -186,6 +188,6 @@ jobs:
|
||||
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 }}"
|
||||
--pr-id "${{ github.event.pull_request.id }}"
|
||||
--branch-name "${{ github.head_ref || github.ref_name }}"
|
||||
--path ./.venv
|
||||
--path ./.venv
|
||||
|
10
.github/workflows/check_generated_pyi.yml
vendored
10
.github/workflows/check_generated_pyi.yml
vendored
@ -6,16 +6,16 @@ concurrency:
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
branches: ["main"]
|
||||
# We don't just trigger on make_pyi.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,7 +25,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup_build_env
|
||||
with:
|
||||
python-version: '3.11.5'
|
||||
python-version: "3.12.8"
|
||||
run-poetry-install: true
|
||||
create-venv-at-path: .venv
|
||||
- run: |
|
||||
|
67
.github/workflows/check_node_latest.yml
vendored
67
.github/workflows/check_node_latest.yml
vendored
@ -1,43 +1,40 @@
|
||||
name: integration-node-latest
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
TELEMETRY_ENABLED: false
|
||||
REFLEX_USE_SYSTEM_NODE: true
|
||||
TELEMETRY_ENABLED: false
|
||||
REFLEX_USE_SYSTEM_NODE: true
|
||||
|
||||
jobs:
|
||||
check_latest_node:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.12']
|
||||
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}}
|
||||
|
||||
|
||||
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}}
|
||||
|
130
.github/workflows/check_outdated_dependencies.yml
vendored
130
.github/workflows/check_outdated_dependencies.yml
vendored
@ -1,88 +1,86 @@
|
||||
name: check-outdated-dependencies
|
||||
|
||||
on:
|
||||
push: # This will trigger the action when a pull request is opened or updated.
|
||||
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.
|
||||
- "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
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: ./.github/actions/setup_build_env
|
||||
with:
|
||||
python-version: '3.9'
|
||||
run-poetry-install: true
|
||||
create-venv-at-path: .venv
|
||||
- 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"
|
||||
- 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
|
||||
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.11'
|
||||
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 psycopg2-binary
|
||||
- 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"
|
||||
- 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' || true)
|
||||
no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
|
||||
# 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
|
||||
|
||||
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
|
||||
|
15
.github/workflows/integration_app_harness.yml
vendored
15
.github/workflows/integration_app_harness.yml
vendored
@ -23,8 +23,8 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
state_manager: ["redis", "memory"]
|
||||
python-version: ["3.11.11", "3.12.8", "3.13.1"]
|
||||
split_index: [1, 2]
|
||||
python-version: ["3.11.5", "3.12.0"]
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-22.04
|
||||
services:
|
||||
@ -47,17 +47,10 @@ jobs:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
run-poetry-install: true
|
||||
create-venv-at-path: .venv
|
||||
- run: poetry run uv pip install pyvirtualdisplay pillow pytest-split
|
||||
- run: poetry run uv pip install pyvirtualdisplay pillow pytest-split pytest-retry
|
||||
- name: Run app harness tests
|
||||
env:
|
||||
SCREENSHOT_DIR: /tmp/screenshots/${{ matrix.state_manager }}/${{ matrix.python-version }}/${{ matrix.split_index }}
|
||||
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
|
||||
run: |
|
||||
poetry run playwright install --with-deps
|
||||
poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}}
|
||||
- uses: actions/upload-artifact@v4
|
||||
name: Upload failed test screenshots
|
||||
if: always()
|
||||
with:
|
||||
name: failed_test_screenshots
|
||||
path: /tmp/screenshots
|
||||
poetry run playwright install chromium
|
||||
poetry run pytest tests/integration --retries 3 --maxfail=5 --splits 2 --group ${{matrix.split_index}}
|
||||
|
63
.github/workflows/integration_tests.yml
vendored
63
.github/workflows/integration_tests.yml
vendored
@ -2,13 +2,13 @@ name: integration-tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
branches: ["main"]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- "**/*.md"
|
||||
pull_request:
|
||||
branches: ['main']
|
||||
branches: ["main"]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- "**/*.md"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
|
||||
@ -27,13 +27,13 @@ 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'
|
||||
NODE_OPTIONS: "--max_old_space_size=8192"
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
|
||||
jobs:
|
||||
example-counter:
|
||||
example-counter-and-nba-proxy:
|
||||
env:
|
||||
OUTPUT_FILE: import_benchmark.json
|
||||
timeout-minutes: 30
|
||||
@ -43,17 +43,17 @@ jobs:
|
||||
matrix:
|
||||
# Show OS combos first in GUI
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0']
|
||||
python-version: ['3.10.16', '3.11.11', '3.12.8', '3.13.1']
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
python-version: '3.10.13'
|
||||
python-version: "3.11.11"
|
||||
- os: windows-latest
|
||||
python-version: '3.9.18'
|
||||
python-version: '3.10.16'
|
||||
include:
|
||||
- os: windows-latest
|
||||
python-version: '3.10.11'
|
||||
python-version: "3.11.9"
|
||||
- os: windows-latest
|
||||
python-version: '3.9.13'
|
||||
python-version: '3.10.11'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@ -73,7 +73,7 @@ jobs:
|
||||
run: |
|
||||
poetry run uv pip install -r requirements.txt
|
||||
- name: Install additional dependencies for DB access
|
||||
run: poetry run uv pip install psycopg2-binary
|
||||
run: poetry run uv pip install psycopg
|
||||
- name: Check export --backend-only before init for counter example
|
||||
working-directory: ./reflex-examples/counter
|
||||
run: |
|
||||
@ -114,7 +114,25 @@ jobs:
|
||||
--benchmark-json "./reflex-examples/counter/${{ env.OUTPUT_FILE }}"
|
||||
--branch-name "${{ github.head_ref || github.ref_name }}" --pr-id "${{ github.event.pull_request.id }}"
|
||||
--app-name "counter"
|
||||
|
||||
- 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:
|
||||
@ -123,10 +141,10 @@ jobs:
|
||||
matrix:
|
||||
# Show OS combos first in GUI
|
||||
os: [ubuntu-latest]
|
||||
python-version: ['3.10.11', '3.11.4']
|
||||
python-version: ["3.11.11", "3.12.8"]
|
||||
|
||||
env:
|
||||
REFLEX_WEB_WINDOWS_OVERRIDE: '1'
|
||||
REFLEX_WEB_WINDOWS_OVERRIDE: "1"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -147,7 +165,7 @@ jobs:
|
||||
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 psycopg2-binary
|
||||
run: poetry run uv pip install psycopg
|
||||
- name: Init Website for reflex-web
|
||||
working-directory: ./reflex-web
|
||||
run: poetry run reflex init
|
||||
@ -171,7 +189,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup_build_env
|
||||
with:
|
||||
python-version: '3.11.4'
|
||||
python-version: "3.11.11"
|
||||
run-poetry-install: true
|
||||
create-venv-at-path: .venv
|
||||
- name: Create app directory
|
||||
@ -190,15 +208,15 @@ jobs:
|
||||
# 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:
|
||||
python-version: ['3.11.5', '3.12.0']
|
||||
runs-on: macos-12
|
||||
# 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
|
||||
@ -216,7 +234,7 @@ jobs:
|
||||
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 psycopg2-binary
|
||||
run: poetry run uv pip install psycopg
|
||||
- name: Init Website for reflex-web
|
||||
working-directory: ./reflex-web
|
||||
run: poetry run reflex init
|
||||
@ -231,4 +249,3 @@ jobs:
|
||||
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
|
||||
--pr-id "${{ github.event.pull_request.id }}" --branch-name "${{ github.head_ref || github.ref_name }}"
|
||||
--app-name "reflex-web" --path ./reflex-web/.web
|
||||
|
34
.github/workflows/performance.yml
vendored
Normal file
34
.github/workflows/performance.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
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 benchmarks/test_evaluate.py --codspeed
|
6
.github/workflows/pre-commit.yml
vendored
6
.github/workflows/pre-commit.yml
vendored
@ -6,12 +6,12 @@ concurrency:
|
||||
|
||||
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,7 +23,7 @@ jobs:
|
||||
with:
|
||||
# running vs. one version of Python is OK
|
||||
# i.e. ruff, black, etc.
|
||||
python-version: 3.11.5
|
||||
python-version: 3.12.8
|
||||
run-poetry-install: true
|
||||
create-venv-at-path: .venv
|
||||
# TODO pre-commit related stuff can be cached too (not a bottleneck yet)
|
||||
|
25
.github/workflows/unit_tests.yml
vendored
25
.github/workflows/unit_tests.yml
vendored
@ -6,13 +6,13 @@ concurrency:
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
branches: ["main"]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- "**/*.md"
|
||||
pull_request:
|
||||
branches: ['main']
|
||||
branches: ["main"]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- "**/*.md"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@ -28,18 +28,18 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0']
|
||||
python-version: ["3.10.16", "3.11.11", "3.12.8", "3.13.1"]
|
||||
# Windows is a bit behind on Python version availability in Github
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
python-version: '3.10.13'
|
||||
python-version: "3.11.11"
|
||||
- os: windows-latest
|
||||
python-version: '3.9.18'
|
||||
python-version: "3.10.16"
|
||||
include:
|
||||
- os: windows-latest
|
||||
python-version: '3.10.11'
|
||||
python-version: "3.11.9"
|
||||
- os: windows-latest
|
||||
python-version: '3.9.13'
|
||||
python-version: "3.10.11"
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# Service containers to run with `runner-job`
|
||||
@ -88,8 +88,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0']
|
||||
runs-on: macos-12
|
||||
# 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
|
||||
@ -105,4 +106,4 @@ jobs:
|
||||
run: |
|
||||
export PYTHONUNBUFFERED=1
|
||||
poetry run uv pip install "pydantic~=1.10"
|
||||
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
||||
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,6 +4,7 @@ assets/external/*
|
||||
dist/*
|
||||
examples/
|
||||
.web
|
||||
.states
|
||||
.idea
|
||||
.vscode
|
||||
.coverage
|
||||
@ -14,3 +15,4 @@ requirements.txt
|
||||
.pyi_generator_last_run
|
||||
.pyi_generator_diff
|
||||
reflex.db
|
||||
.codspeed
|
@ -3,7 +3,7 @@ fail_fast: true
|
||||
repos:
|
||||
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.8.2
|
||||
rev: v0.9.3
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
args: [reflex, tests]
|
||||
@ -11,6 +11,12 @@ repos:
|
||||
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:
|
||||
@ -18,11 +24,12 @@ repos:
|
||||
name: update-pyi-files
|
||||
always_run: true
|
||||
language: system
|
||||
require_serial: true
|
||||
description: 'Update pyi files as needed'
|
||||
entry: python3 scripts/make_pyi.py
|
||||
|
||||
- repo: https://github.com/RobertCraigie/pyright-python
|
||||
rev: v1.1.313
|
||||
rev: v1.1.393
|
||||
hooks:
|
||||
- id: pyright
|
||||
args: [reflex, tests]
|
||||
|
@ -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, socio-economic status,
|
||||
identity and expression, level of experience, education, socioeconomic 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.9
|
||||
- Python >= 3.10
|
||||
- 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:**
|
||||
@ -87,7 +87,7 @@ poetry run ruff format .
|
||||
```
|
||||
|
||||
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.9.
|
||||
Note that pre-commit will only be installed when you use a Python version >= 3.10.
|
||||
|
||||
``` bash
|
||||
pre-commit install
|
||||
|
@ -34,7 +34,7 @@ See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architectu
|
||||
|
||||
## ⚙️ Installation
|
||||
|
||||
Open a terminal and run (Requires Python 3.9+):
|
||||
Open a terminal and run (Requires Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
@ -249,7 +249,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 [CONTIBUTING.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 [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
||||
|
||||
|
||||
## All Thanks To Our Contributors:
|
||||
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from utils import send_data_to_posthog
|
||||
|
||||
@ -18,7 +19,7 @@ def extract_stats_from_json(json_file: str) -> list[dict]:
|
||||
Returns:
|
||||
list[dict]: The stats for each test.
|
||||
"""
|
||||
with open(json_file, "r") as file:
|
||||
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
|
||||
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from utils import send_data_to_posthog
|
||||
|
||||
@ -18,7 +19,7 @@ def extract_stats_from_json(json_file: str) -> dict:
|
||||
Returns:
|
||||
dict: The stats for each test.
|
||||
"""
|
||||
with open(json_file, "r") as file:
|
||||
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
|
||||
|
@ -34,13 +34,13 @@ def render_component(num: int):
|
||||
rx.box(
|
||||
rx.accordion.root(
|
||||
rx.accordion.item(
|
||||
header="Full Ingredients", # type: ignore
|
||||
content="Yes. It's built with accessibility in mind.", # type: ignore
|
||||
header="Full Ingredients",
|
||||
content="Yes. It's built with accessibility in mind.",
|
||||
font_size="3em",
|
||||
),
|
||||
rx.accordion.item(
|
||||
header="Applications", # type: ignore
|
||||
content="Yes. It's unstyled by default, giving you freedom over the look and feel.", # type: ignore
|
||||
header="Applications",
|
||||
content="Yes. It's unstyled by default, giving you freedom over the look and feel.",
|
||||
),
|
||||
collapsible=True,
|
||||
variant="ghost",
|
||||
@ -122,7 +122,7 @@ def AppWithTenComponentsOnePage():
|
||||
def index() -> rx.Component:
|
||||
return rx.center(rx.vstack(*render_component(1)))
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
app.add_page(index)
|
||||
|
||||
|
||||
@ -133,7 +133,7 @@ def AppWithHundredComponentOnePage():
|
||||
def index() -> rx.Component:
|
||||
return rx.center(rx.vstack(*render_component(100)))
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
app.add_page(index)
|
||||
|
||||
|
||||
@ -144,7 +144,7 @@ def AppWithThousandComponentsOnePage():
|
||||
def index() -> rx.Component:
|
||||
return rx.center(rx.vstack(*render_component(1000)))
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
app.add_page(index)
|
||||
|
||||
|
||||
@ -166,9 +166,9 @@ def app_with_10_components(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithTenComponentsOnePage,
|
||||
render_component=render_component, # type: ignore
|
||||
render_component=render_component, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
) # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@ -189,9 +189,9 @@ def app_with_100_components(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithHundredComponentOnePage,
|
||||
render_component=render_component, # type: ignore
|
||||
render_component=render_component, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
) # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@ -212,9 +212,9 @@ def app_with_1000_components(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithThousandComponentsOnePage,
|
||||
render_component=render_component, # type: ignore
|
||||
render_component=render_component, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
) # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
||||
|
@ -28,7 +28,7 @@ def render_multiple_pages(app, num: int):
|
||||
"""
|
||||
from typing import Tuple
|
||||
|
||||
from rxconfig import config # type: ignore
|
||||
from rxconfig import config # pyright: ignore [reportMissingImports]
|
||||
|
||||
import reflex as rx
|
||||
|
||||
@ -74,13 +74,13 @@ def render_multiple_pages(app, num: int):
|
||||
rx.select(
|
||||
["C", "PF", "SF", "PG", "SG"],
|
||||
placeholder="Select a position. (All)",
|
||||
on_change=State.set_position, # type: ignore
|
||||
on_change=State.set_position, # pyright: ignore [reportAttributeAccessIssue]
|
||||
size="3",
|
||||
),
|
||||
rx.select(
|
||||
college,
|
||||
placeholder="Select a college. (All)",
|
||||
on_change=State.set_college, # type: ignore
|
||||
on_change=State.set_college, # pyright: ignore [reportAttributeAccessIssue]
|
||||
size="3",
|
||||
),
|
||||
),
|
||||
@ -95,7 +95,7 @@ def render_multiple_pages(app, num: int):
|
||||
default_value=[18, 50],
|
||||
min=18,
|
||||
max=50,
|
||||
on_value_commit=State.set_age, # type: ignore
|
||||
on_value_commit=State.set_age, # pyright: ignore [reportAttributeAccessIssue]
|
||||
),
|
||||
align_items="left",
|
||||
width="100%",
|
||||
@ -110,7 +110,7 @@ def render_multiple_pages(app, num: int):
|
||||
default_value=[0, 25000000],
|
||||
min=0,
|
||||
max=25000000,
|
||||
on_value_commit=State.set_salary, # type: ignore
|
||||
on_value_commit=State.set_salary, # pyright: ignore [reportAttributeAccessIssue]
|
||||
),
|
||||
align_items="left",
|
||||
width="100%",
|
||||
@ -130,7 +130,7 @@ def render_multiple_pages(app, num: int):
|
||||
|
||||
def AppWithOnePage():
|
||||
"""A reflex app with one page."""
|
||||
from rxconfig import config # type: ignore
|
||||
from rxconfig import config # pyright: ignore [reportMissingImports]
|
||||
|
||||
import reflex as rx
|
||||
|
||||
@ -162,7 +162,7 @@ def AppWithOnePage():
|
||||
height="100vh",
|
||||
)
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
app.add_page(index)
|
||||
|
||||
|
||||
@ -170,7 +170,7 @@ def AppWithTenPages():
|
||||
"""A reflex app with 10 pages."""
|
||||
import reflex as rx
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
render_multiple_pages(app, 10)
|
||||
|
||||
|
||||
@ -178,7 +178,7 @@ def AppWithHundredPages():
|
||||
"""A reflex app with 100 pages."""
|
||||
import reflex as rx
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
render_multiple_pages(app, 100)
|
||||
|
||||
|
||||
@ -186,7 +186,7 @@ def AppWithThousandPages():
|
||||
"""A reflex app with Thousand pages."""
|
||||
import reflex as rx
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
render_multiple_pages(app, 1000)
|
||||
|
||||
|
||||
@ -194,7 +194,7 @@ def AppWithTenThousandPages():
|
||||
"""A reflex app with ten thousand pages."""
|
||||
import reflex as rx
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
render_multiple_pages(app, 10000)
|
||||
|
||||
|
||||
@ -232,7 +232,7 @@ def app_with_ten_pages(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithTenPages,
|
||||
render_comp=render_multiple_pages, # type: ignore
|
||||
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
)
|
||||
|
||||
@ -255,9 +255,9 @@ def app_with_hundred_pages(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithHundredPages,
|
||||
render_comp=render_multiple_pages, # type: ignore
|
||||
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
) # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@ -278,9 +278,9 @@ def app_with_thousand_pages(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithThousandPages,
|
||||
render_comp=render_multiple_pages, # type: ignore
|
||||
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
) # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@ -301,9 +301,9 @@ def app_with_ten_thousand_pages(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithTenThousandPages,
|
||||
render_comp=render_multiple_pages, # type: ignore
|
||||
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
) # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
||||
|
231
benchmarks/test_evaluate.py
Normal file
231
benchmarks/test_evaluate.py
Normal file
@ -0,0 +1,231 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
|
||||
import reflex as rx
|
||||
|
||||
|
||||
class SideBarState(rx.State):
|
||||
"""State for the side bar."""
|
||||
|
||||
current_page: rx.Field[str] = rx.field("/")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SideBarPage:
|
||||
"""A page in the side bar."""
|
||||
|
||||
title: str
|
||||
href: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SideBarSection:
|
||||
"""A section in the side bar."""
|
||||
|
||||
name: str
|
||||
icon: str
|
||||
pages: tuple[SideBarPage, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Category:
|
||||
"""A category in the side bar."""
|
||||
|
||||
name: str
|
||||
href: str
|
||||
sections: tuple[SideBarSection, ...]
|
||||
|
||||
|
||||
SIDE_BAR = (
|
||||
Category(
|
||||
name="General",
|
||||
href="/",
|
||||
sections=(
|
||||
SideBarSection(
|
||||
name="Home",
|
||||
icon="home",
|
||||
pages=(
|
||||
SideBarPage(title="Home", href="/"),
|
||||
SideBarPage(title="Contact", href="/contact"),
|
||||
),
|
||||
),
|
||||
SideBarSection(
|
||||
name="About",
|
||||
icon="info",
|
||||
pages=(
|
||||
SideBarPage(title="About", href="/about"),
|
||||
SideBarPage(title="FAQ", href="/faq"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Category(
|
||||
name="Projects",
|
||||
href="/projects",
|
||||
sections=(
|
||||
SideBarSection(
|
||||
name="Python",
|
||||
icon="worm",
|
||||
pages=(
|
||||
SideBarPage(title="Python", href="/projects/python"),
|
||||
SideBarPage(title="Django", href="/projects/django"),
|
||||
SideBarPage(title="Flask", href="/projects/flask"),
|
||||
SideBarPage(title="FastAPI", href="/projects/fastapi"),
|
||||
SideBarPage(title="Pyramid", href="/projects/pyramid"),
|
||||
SideBarPage(title="Tornado", href="/projects/tornado"),
|
||||
SideBarPage(title="TurboGears", href="/projects/turbogears"),
|
||||
SideBarPage(title="Web2py", href="/projects/web2py"),
|
||||
SideBarPage(title="Zope", href="/projects/zope"),
|
||||
SideBarPage(title="Plone", href="/projects/plone"),
|
||||
SideBarPage(title="Quixote", href="/projects/quixote"),
|
||||
SideBarPage(title="Bottle", href="/projects/bottle"),
|
||||
SideBarPage(title="CherryPy", href="/projects/cherrypy"),
|
||||
SideBarPage(title="Falcon", href="/projects/falcon"),
|
||||
SideBarPage(title="Sanic", href="/projects/sanic"),
|
||||
SideBarPage(title="Starlette", href="/projects/starlette"),
|
||||
),
|
||||
),
|
||||
SideBarSection(
|
||||
name="JavaScript",
|
||||
icon="banana",
|
||||
pages=(
|
||||
SideBarPage(title="JavaScript", href="/projects/javascript"),
|
||||
SideBarPage(title="Angular", href="/projects/angular"),
|
||||
SideBarPage(title="React", href="/projects/react"),
|
||||
SideBarPage(title="Vue", href="/projects/vue"),
|
||||
SideBarPage(title="Ember", href="/projects/ember"),
|
||||
SideBarPage(title="Backbone", href="/projects/backbone"),
|
||||
SideBarPage(title="Meteor", href="/projects/meteor"),
|
||||
SideBarPage(title="Svelte", href="/projects/svelte"),
|
||||
SideBarPage(title="Preact", href="/projects/preact"),
|
||||
SideBarPage(title="Mithril", href="/projects/mithril"),
|
||||
SideBarPage(title="Aurelia", href="/projects/aurelia"),
|
||||
SideBarPage(title="Polymer", href="/projects/polymer"),
|
||||
SideBarPage(title="Knockout", href="/projects/knockout"),
|
||||
SideBarPage(title="Dojo", href="/projects/dojo"),
|
||||
SideBarPage(title="Riot", href="/projects/riot"),
|
||||
SideBarPage(title="Alpine", href="/projects/alpine"),
|
||||
SideBarPage(title="Stimulus", href="/projects/stimulus"),
|
||||
SideBarPage(title="Marko", href="/projects/marko"),
|
||||
SideBarPage(title="Sapper", href="/projects/sapper"),
|
||||
SideBarPage(title="Nuxt", href="/projects/nuxt"),
|
||||
SideBarPage(title="Next", href="/projects/next"),
|
||||
SideBarPage(title="Gatsby", href="/projects/gatsby"),
|
||||
SideBarPage(title="Gridsome", href="/projects/gridsome"),
|
||||
SideBarPage(title="Nest", href="/projects/nest"),
|
||||
SideBarPage(title="Express", href="/projects/express"),
|
||||
SideBarPage(title="Koa", href="/projects/koa"),
|
||||
SideBarPage(title="Hapi", href="/projects/hapi"),
|
||||
SideBarPage(title="LoopBack", href="/projects/loopback"),
|
||||
SideBarPage(title="Feathers", href="/projects/feathers"),
|
||||
SideBarPage(title="Sails", href="/projects/sails"),
|
||||
SideBarPage(title="Adonis", href="/projects/adonis"),
|
||||
SideBarPage(title="Meteor", href="/projects/meteor"),
|
||||
SideBarPage(title="Derby", href="/projects/derby"),
|
||||
SideBarPage(title="Socket.IO", href="/projects/socketio"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def side_bar_page(page: SideBarPage):
|
||||
return rx.box(
|
||||
rx.link(
|
||||
page.title,
|
||||
href=page.href,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def side_bar_section(section: SideBarSection):
|
||||
return rx.accordion.item(
|
||||
rx.accordion.header(
|
||||
rx.accordion.trigger(
|
||||
rx.hstack(
|
||||
rx.hstack(
|
||||
rx.icon(section.icon),
|
||||
section.name,
|
||||
align="center",
|
||||
),
|
||||
rx.accordion.icon(),
|
||||
width="100%",
|
||||
justify="between",
|
||||
)
|
||||
)
|
||||
),
|
||||
rx.accordion.content(
|
||||
rx.vstack(
|
||||
*map(side_bar_page, section.pages),
|
||||
),
|
||||
border_inline_start="1px solid",
|
||||
padding_inline_start="1em",
|
||||
margin_inline_start="1.5em",
|
||||
),
|
||||
value=section.name,
|
||||
width="100%",
|
||||
variant="ghost",
|
||||
)
|
||||
|
||||
|
||||
def side_bar_category(category: Category):
|
||||
selected_section = cast(
|
||||
rx.Var,
|
||||
rx.match(
|
||||
SideBarState.current_page,
|
||||
*[
|
||||
(
|
||||
section.name,
|
||||
section.name,
|
||||
)
|
||||
for section in category.sections
|
||||
],
|
||||
None,
|
||||
),
|
||||
)
|
||||
return rx.vstack(
|
||||
rx.heading(
|
||||
rx.link(
|
||||
category.name,
|
||||
href=category.href,
|
||||
),
|
||||
size="5",
|
||||
),
|
||||
rx.accordion.root(
|
||||
*map(side_bar_section, category.sections),
|
||||
default_value=selected_section.to(str),
|
||||
variant="ghost",
|
||||
width="100%",
|
||||
collapsible=True,
|
||||
type="multiple",
|
||||
),
|
||||
width="100%",
|
||||
)
|
||||
|
||||
|
||||
def side_bar():
|
||||
return rx.vstack(
|
||||
*map(side_bar_category, SIDE_BAR),
|
||||
width="fit-content",
|
||||
)
|
||||
|
||||
|
||||
LOREM_IPSUM = "Lorem ipsum dolor sit amet, dolor ut dolore pariatur aliqua enim tempor sed. Labore excepteur sed exercitation. Ullamco aliquip lorem sunt enim in incididunt. Magna anim officia sint cillum labore. Ut eu non dolore minim nostrud magna eu, aute ex in incididunt irure eu. Fugiat et magna magna est excepteur eiusmod minim. Quis eiusmod et non pariatur dolor veniam incididunt, eiusmod irure enim sed dolor lorem pariatur do. Occaecat duis irure excepteur dolore. Proident ut laborum pariatur sit sit, nisi nostrud voluptate magna commodo laborum esse velit. Voluptate non minim deserunt adipiscing irure deserunt cupidatat. Laboris veniam commodo incididunt veniam lorem occaecat, fugiat ipsum dolor cupidatat. Ea officia sed eu excepteur culpa adipiscing, tempor consectetur ullamco eu. Anim ex proident nulla sunt culpa, voluptate veniam proident est adipiscing sint elit velit. Laboris adipiscing est culpa cillum magna. Sit veniam nulla nulla, aliqua eiusmod commodo lorem cupidatat commodo occaecat. Fugiat cillum dolor incididunt mollit eiusmod sint. Non lorem dolore labore excepteur minim laborum sed. Irure nisi do lorem nulla sunt commodo, deserunt quis mollit consectetur minim et esse est, proident nostrud officia enim sed reprehenderit. Magna cillum consequat aute reprehenderit duis sunt ullamco. Labore qui mollit voluptate. Duis dolor sint aute amet aliquip officia, est non mollit tempor enim quis fugiat, eu do culpa consectetur magna. Do ullamco aliqua voluptate culpa excepteur reprehenderit reprehenderit. Occaecat nulla sit est magna. Deserunt ea voluptate veniam cillum. Amet cupidatat duis est tempor fugiat ex eu, officia est sunt consectetur labore esse exercitation. Nisi cupidatat irure est nisi. Officia amet eu veniam reprehenderit. In amet incididunt tempor commodo ea labore. Mollit dolor aliquip excepteur, voluptate aute occaecat id officia proident. Ullamco est amet tempor. Proident aliquip proident mollit do aliquip ipsum, culpa quis aute id irure. Velit excepteur cillum cillum ut cupidatat. Occaecat qui elit esse nulla minim. Consequat velit id ad pariatur tempor. Eiusmod deserunt aliqua ex sed quis non. Dolor sint commodo ex in deserunt nostrud excepteur, pariatur ex aliqua anim adipiscing amet proident. Laboris eu laborum magna lorem ipsum fugiat velit."
|
||||
|
||||
|
||||
def complicated_page():
|
||||
return rx.hstack(
|
||||
side_bar(),
|
||||
rx.box(
|
||||
rx.heading("Complicated Page", size="1"),
|
||||
rx.text(LOREM_IPSUM),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.benchmark
|
||||
def test_component_init():
|
||||
complicated_page()
|
@ -27,7 +27,7 @@ FROM python:3.13 as init
|
||||
|
||||
ARG uv=/root/.local/bin/uv
|
||||
|
||||
# Install `uv` for faster package boostrapping
|
||||
# Install `uv` for faster package bootstrapping
|
||||
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
|
||||
RUN /install.sh && rm /install.sh
|
||||
|
||||
@ -52,7 +52,7 @@ 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 psycopg2 (skip if not using postgres).
|
||||
# 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
|
||||
|
@ -6,7 +6,7 @@ FROM python:3.13 as init
|
||||
|
||||
ARG uv=/root/.local/bin/uv
|
||||
|
||||
# Install `uv` for faster package boostrapping
|
||||
# Install `uv` for faster package bootstrapping
|
||||
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
|
||||
RUN /install.sh && rm /install.sh
|
||||
|
||||
@ -39,7 +39,7 @@ 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 psycopg2 (skip if not using postgres).
|
||||
# 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
|
||||
|
@ -15,7 +15,7 @@ services:
|
||||
|
||||
app:
|
||||
environment:
|
||||
DB_URL: postgresql+psycopg2://postgres:secret@db/postgres
|
||||
DB_URL: postgresql+psycopg://postgres:secret@db/postgres
|
||||
REDIS_URL: redis://redis:6379
|
||||
depends_on:
|
||||
- db
|
||||
|
@ -34,7 +34,7 @@ Auf unserer [Architektur-Seite](https://reflex.dev/blog/2024-03-21-reflex-archit
|
||||
|
||||
## ⚙️ Installation
|
||||
|
||||
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.9+):
|
||||
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -35,7 +35,7 @@ Consulta nuestra [página de arquitectura](https://reflex.dev/blog/2024-03-21-re
|
||||
|
||||
## ⚙️ Instalación
|
||||
|
||||
Abra un terminal y ejecute (Requiere Python 3.9+):
|
||||
Abra un terminal y ejecute (Requiere Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -35,7 +35,7 @@ Reflex के अंदर के कामकाज को जानने क
|
||||
|
||||
## ⚙️ इंस्टॉलेशन (Installation)
|
||||
|
||||
एक टर्मिनल खोलें और चलाएं (Python 3.9+ की आवश्यकता है):
|
||||
एक टर्मिनल खोलें और चलाएं (Python 3.10+ की आवश्यकता है):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
## ⚙️ Installazione
|
||||
|
||||
Apri un terminale ed esegui (Richiede Python 3.9+):
|
||||
Apri un terminale ed esegui (Richiede Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -37,7 +37,7 @@ Reflex がどのように動作しているかを知るには、[アーキテク
|
||||
|
||||
## ⚙️ インストール
|
||||
|
||||
ターミナルを開いて以下のコマンドを実行してください。(Python 3.9 以上が必要です。):
|
||||
ターミナルを開いて以下のコマンドを実行してください。(Python 3.10 以上が必要です。):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -20,7 +20,7 @@
|
||||
---
|
||||
## ⚙️ 설치
|
||||
|
||||
터미널을 열고 실행하세요. (Python 3.9+ 필요):
|
||||
터미널을 열고 실행하세요. (Python 3.10+ 필요):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -34,7 +34,7 @@
|
||||
|
||||
## ⚙️ Installation - نصب و راه اندازی
|
||||
|
||||
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.9+):
|
||||
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -21,7 +21,7 @@
|
||||
---
|
||||
## ⚙️ Instalação
|
||||
|
||||
Abra um terminal e execute (Requer Python 3.9+):
|
||||
Abra um terminal e execute (Requer Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
## ⚙️ Kurulum
|
||||
|
||||
Bir terminal açın ve çalıştırın (Python 3.9+ gerekir):
|
||||
Bir terminal açın ve çalıştırın (Python 3.10+ gerekir):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -34,7 +34,7 @@ Các tính năng chính:
|
||||
|
||||
## ⚙️ Cài đặt
|
||||
|
||||
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.9+):
|
||||
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -34,7 +34,7 @@ Reflex 是一个使用纯Python构建全栈web应用的库。
|
||||
|
||||
## ⚙️ 安装
|
||||
|
||||
打开一个终端并且运行(要求Python3.9+):
|
||||
打开一个终端并且运行(要求Python3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -36,7 +36,7 @@ Reflex 是一個可以用純 Python 構建全端網頁應用程式的函式庫
|
||||
|
||||
## ⚙️ 安裝
|
||||
|
||||
開啟一個終端機並且執行 (需要 Python 3.9+):
|
||||
開啟一個終端機並且執行 (需要 Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
2033
poetry.lock
generated
2033
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,32 +1,24 @@
|
||||
[tool.poetry]
|
||||
name = "reflex"
|
||||
version = "0.6.7dev1"
|
||||
version = "0.7.0dev1"
|
||||
description = "Web apps in pure Python."
|
||||
license = "Apache-2.0"
|
||||
authors = [
|
||||
"Nikhil Rao <nikhil@reflex.dev>",
|
||||
"Alek Petuskey <alek@reflex.dev>",
|
||||
"Masen Furer <masen@reflex.dev>",
|
||||
"Elijah Ahianyo <elijah@reflex.dev>",
|
||||
"Thomas Brandého <thomas@reflex.dev>",
|
||||
"Nikhil Rao <nikhil@reflex.dev>",
|
||||
"Alek Petuskey <alek@reflex.dev>",
|
||||
"Masen Furer <masen@reflex.dev>",
|
||||
"Elijah Ahianyo <elijah@reflex.dev>",
|
||||
"Thomas Brandého <thomas@reflex.dev>",
|
||||
]
|
||||
readme = "README.md"
|
||||
homepage = "https://reflex.dev"
|
||||
repository = "https://github.com/reflex-dev/reflex"
|
||||
documentation = "https://reflex.dev/docs/getting-started/introduction"
|
||||
keywords = [
|
||||
"web",
|
||||
"framework",
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
]
|
||||
packages = [
|
||||
{include = "reflex"}
|
||||
]
|
||||
keywords = ["web", "framework"]
|
||||
classifiers = ["Development Status :: 4 - Beta"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
python = ">=3.10, <4.0"
|
||||
fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
|
||||
gunicorn = ">=20.1.0,<24.0"
|
||||
jinja2 = ">=3.1.2,<4.0"
|
||||
@ -42,14 +34,14 @@ uvicorn = ">=0.20.0"
|
||||
starlette-admin = ">=0.11.0,<1.0"
|
||||
alembic = ">=1.11.1,<2.0"
|
||||
platformdirs = ">=3.10.0,<5.0"
|
||||
distro = {version = ">=1.8.0,<2.0", platform = "linux"}
|
||||
distro = { version = ">=1.8.0,<2.0", platform = "linux" }
|
||||
python-engineio = "!=4.6.0"
|
||||
wrapt = [
|
||||
{version = ">=1.14.0,<2.0", python = ">=3.11"},
|
||||
{version = ">=1.11.0,<2.0", python = "<3.11"},
|
||||
{ version = ">=1.14.0,<2.0", python = ">=3.11" },
|
||||
{ version = ">=1.11.0,<2.0", python = "<3.11" },
|
||||
]
|
||||
packaging = ">=23.1,<25.0"
|
||||
reflex-hosting-cli = ">=0.1.29,<2.0"
|
||||
reflex-hosting-cli = ">=0.1.29"
|
||||
charset-normalizer = ">=3.3.2,<4.0"
|
||||
wheel = ">=0.42.0,<1.0"
|
||||
build = ">=1.0.3,<2.0"
|
||||
@ -58,19 +50,18 @@ httpx = ">=0.25.1,<1.0"
|
||||
twine = ">=4.0.0,<7.0"
|
||||
tomlkit = ">=0.12.4,<1.0"
|
||||
lazy_loader = ">=0.4"
|
||||
reflex-chakra = ">=0.6.0"
|
||||
typing_extensions = ">=4.6.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = ">=7.1.2,<9.0"
|
||||
pytest-mock = ">=3.10.0,<4.0"
|
||||
pyright = ">=1.1.229,<1.1.335"
|
||||
pyright = ">=1.1.392, <1.2"
|
||||
darglint = ">=1.8.1,<2.0"
|
||||
dill = ">=0.3.8"
|
||||
toml = ">=0.10.2,<1.0"
|
||||
pytest-asyncio = ">=0.24.0"
|
||||
pytest-cov = ">=4.0.0,<7.0"
|
||||
ruff = "0.8.2"
|
||||
ruff = "0.9.3"
|
||||
pandas = ">=2.1.1,<3.0"
|
||||
pillow = ">=10.0.0,<12.0"
|
||||
plotly = ">=5.13.0,<6.0"
|
||||
@ -80,6 +71,7 @@ selenium = ">=4.11.0,<5.0"
|
||||
pytest-benchmark = ">=4.0.0,<6.0"
|
||||
playwright = ">=1.46.0"
|
||||
pytest-playwright = ">=0.5.1"
|
||||
pytest-codspeed = "^3.1.2"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
reflex = "reflex.reflex:cli"
|
||||
@ -89,21 +81,30 @@ requires = ["poetry-core>=1.5.1"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.pyright]
|
||||
reportIncompatibleMethodOverride = false
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
output-format = "concise"
|
||||
lint.isort.split-on-trailing-comma = false
|
||||
lint.select = ["B", "D", "E", "F", "I", "SIM", "W", "RUF"]
|
||||
lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012"]
|
||||
lint.select = ["ANN001","B", "C4", "D", "E", "ERA", "F", "FURB", "I", "N", "PERF", "PGH", "PTH", "RUF", "SIM", "T", "TRY", "W"]
|
||||
lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF008", "RUF012", "TRY0"]
|
||||
lint.pydocstyle.convention = "google"
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"__init__.py" = ["F401"]
|
||||
"tests/*.py" = ["D100", "D103", "D104", "B018"]
|
||||
"tests/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"]
|
||||
"benchmarks/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"]
|
||||
"reflex/.templates/*.py" = ["D100", "D103", "D104"]
|
||||
"*.pyi" = ["D301", "D415", "D417", "D418", "E742"]
|
||||
"*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N", "PGH"]
|
||||
"pyi_generator.py" = ["N802"]
|
||||
"reflex/constants/*.py" = ["N"]
|
||||
"*/blank.py" = ["I001"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
asyncio_mode = "auto"
|
||||
|
||||
[tool.codespell]
|
||||
skip = "docs/*,*.html,examples/*, *.pyi, poetry.lock"
|
||||
ignore-words-list = "te, TreeE"
|
||||
|
@ -8,7 +8,7 @@ version = "0.0.1"
|
||||
description = "Reflex custom component {{ module_name }}"
|
||||
readme = "README.md"
|
||||
license = { text = "Apache-2.0" }
|
||||
requires-python = ">=3.9"
|
||||
requires-python = ">=3.10"
|
||||
authors = [{ name = "", email = "YOUREMAIL@domain.com" }]
|
||||
keywords = ["reflex","reflex-custom-components"]
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% extends "web/pages/base_page.js.jinja2" %}
|
||||
{% from "web/pages/macros.js.jinja2" import renderHooks %}
|
||||
|
||||
{% block early_imports %}
|
||||
import '$/styles/styles.css'
|
||||
@ -18,10 +19,7 @@ import * as {{library_alias}} from "{{library_path}}";
|
||||
|
||||
{% block export %}
|
||||
function AppWrap({children}) {
|
||||
|
||||
{% for hook in hooks %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
{{ renderHooks(hooks) }}
|
||||
|
||||
return (
|
||||
{{utils.render(render, indent_width=0)}}
|
||||
@ -40,13 +38,13 @@ export default function MyApp({ Component, pageProps }) {
|
||||
}, []);
|
||||
return (
|
||||
<ThemeProvider defaultTheme={ defaultColorMode } attribute="class">
|
||||
<AppWrap>
|
||||
<StateProvider>
|
||||
<EventLoopProvider>
|
||||
<Component {...pageProps} />
|
||||
</EventLoopProvider>
|
||||
</StateProvider>
|
||||
</AppWrap>
|
||||
<StateProvider>
|
||||
<EventLoopProvider>
|
||||
<AppWrap>
|
||||
<Component {...pageProps} />
|
||||
</AppWrap>
|
||||
</EventLoopProvider>
|
||||
</StateProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% extends "web/pages/base_page.js.jinja2" %}
|
||||
|
||||
{% from "web/pages/macros.js.jinja2" import renderHooks %}
|
||||
{% block export %}
|
||||
{% for component in components %}
|
||||
|
||||
@ -8,9 +8,8 @@
|
||||
{% endfor %}
|
||||
|
||||
export const {{component.name}} = memo(({ {{-component.props|join(", ")-}} }) => {
|
||||
{% for hook in component.hooks %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
{{ renderHooks(component.hooks) }}
|
||||
|
||||
return(
|
||||
{{utils.render(component.render)}}
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% extends "web/pages/base_page.js.jinja2" %}
|
||||
{% from "web/pages/macros.js.jinja2" import renderHooks %}
|
||||
|
||||
{% block declaration %}
|
||||
{% for custom_code in custom_codes %}
|
||||
@ -8,9 +9,7 @@
|
||||
|
||||
{% block export %}
|
||||
export default function Component() {
|
||||
{% for hook in hooks %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
{{ renderHooks(hooks)}}
|
||||
|
||||
return (
|
||||
{{utils.render(render, indent_width=0)}}
|
||||
|
38
reflex/.templates/jinja/web/pages/macros.js.jinja2
Normal file
38
reflex/.templates/jinja/web/pages/macros.js.jinja2
Normal file
@ -0,0 +1,38 @@
|
||||
{% macro renderHooks(hooks) %}
|
||||
{% set sorted_hooks = sort_hooks(hooks) %}
|
||||
|
||||
{# Render the grouped hooks #}
|
||||
{% for hook, _ in sorted_hooks[const.hook_position.INTERNAL] %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
|
||||
{% for hook, _ in sorted_hooks[const.hook_position.PRE_TRIGGER] %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
|
||||
{% for hook, _ in sorted_hooks[const.hook_position.POST_TRIGGER] %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro renderHooksWithMemo(hooks, memo)%}
|
||||
{% set sorted_hooks = sort_hooks(hooks) %}
|
||||
|
||||
{# Render the grouped hooks #}
|
||||
{% for hook, _ in sorted_hooks[const.hook_position.INTERNAL] %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
|
||||
{% for hook, _ in sorted_hooks[const.hook_position.PRE_TRIGGER] %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
|
||||
{% for hook in memo %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
|
||||
{% for hook, _ in sorted_hooks[const.hook_position.POST_TRIGGER] %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
|
||||
{% endmacro %}
|
@ -1,18 +1,10 @@
|
||||
{% import 'web/pages/utils.js.jinja2' as utils %}
|
||||
{% from 'web/pages/macros.js.jinja2' import renderHooksWithMemo %}
|
||||
{% set all_hooks = component._get_all_hooks() %}
|
||||
|
||||
export function {{tag_name}} () {
|
||||
{% for hook in component._get_all_hooks_internal() %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
|
||||
{% for hook in memo_trigger_hooks %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
|
||||
{% for hook in component._get_all_hooks() %}
|
||||
{{ hook }}
|
||||
{% endfor %}
|
||||
|
||||
{{ renderHooksWithMemo(all_hooks, memo_trigger_hooks) }}
|
||||
|
||||
return (
|
||||
{{utils.render(component.render(), indent_width=0)}}
|
||||
)
|
||||
|
@ -86,11 +86,11 @@
|
||||
{% for condition in case[:-1] %}
|
||||
case JSON.stringify({{ condition._js_expr }}):
|
||||
{% endfor %}
|
||||
return {{ case[-1] }};
|
||||
return {{ render(case[-1]) }};
|
||||
break;
|
||||
{% endfor %}
|
||||
default:
|
||||
return {{ component.default }};
|
||||
return {{ render(component.default) }};
|
||||
break;
|
||||
}
|
||||
})()
|
||||
|
@ -28,7 +28,7 @@ export const state_name = "{{state_name}}"
|
||||
|
||||
export const exception_state_name = "{{const.frontend_exception_state}}"
|
||||
|
||||
// Theses events are triggered on initial load and each page navigation.
|
||||
// These events are triggered on initial load and each page navigation.
|
||||
export const onLoadInternalEvent = () => {
|
||||
const internal_events = [];
|
||||
|
||||
|
@ -16,10 +16,7 @@ export default function RadixThemesColorModeProvider({ children }) {
|
||||
if (isDevMode) {
|
||||
const lastCompiledTimeInLocalStorage =
|
||||
localStorage.getItem("last_compiled_time");
|
||||
if (
|
||||
lastCompiledTimeInLocalStorage &&
|
||||
lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp
|
||||
) {
|
||||
if (lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp) {
|
||||
// on app startup, make sure the application color mode is persisted correctly.
|
||||
setTheme(defaultColorMode);
|
||||
localStorage.setItem("last_compiled_time", lastCompiledTimeStamp);
|
||||
|
@ -3,6 +3,7 @@ import axios from "axios";
|
||||
import io from "socket.io-client";
|
||||
import JSON5 from "json5";
|
||||
import env from "$/env.json";
|
||||
import reflexEnvironment from "$/reflex.json";
|
||||
import Cookies from "universal-cookie";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Router, { useRouter } from "next/router";
|
||||
@ -40,9 +41,6 @@ let event_processing = false;
|
||||
// Array holding pending events to be processed.
|
||||
const event_queue = [];
|
||||
|
||||
// Pending upload promises, by id
|
||||
const upload_controllers = {};
|
||||
|
||||
/**
|
||||
* Generate a UUID (Used for session tokens).
|
||||
* Taken from: https://stackoverflow.com/questions/105034/how-do-i-create-a-guid-uuid
|
||||
@ -108,6 +106,18 @@ export const getBackendURL = (url_str) => {
|
||||
return endpoint;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the backend is disabled.
|
||||
*
|
||||
* @returns True if the backend is disabled, false otherwise.
|
||||
*/
|
||||
export const isBackendDisabled = () => {
|
||||
const cookie = document.cookie
|
||||
.split("; ")
|
||||
.find((row) => row.startsWith("backend-enabled="));
|
||||
return cookie !== undefined && cookie.split("=")[1] == "false";
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if any event in the event queue is stateful.
|
||||
*
|
||||
@ -211,11 +221,16 @@ export const applyEvent = async (event, socket) => {
|
||||
if (event.name == "_download") {
|
||||
const a = document.createElement("a");
|
||||
a.hidden = true;
|
||||
a.href = event.payload.url;
|
||||
// Special case when linking to uploaded files
|
||||
a.href = event.payload.url.replace(
|
||||
"${getBackendURL(env.UPLOAD)}",
|
||||
getBackendURL(env.UPLOAD)
|
||||
);
|
||||
if (a.href.includes("getBackendURL(env.UPLOAD)")) {
|
||||
a.href = eval?.(
|
||||
event.payload.url.replace(
|
||||
"getBackendURL(env.UPLOAD)",
|
||||
`"${getBackendURL(env.UPLOAD)}"`,
|
||||
),
|
||||
);
|
||||
}
|
||||
a.download = event.payload.filename;
|
||||
a.click();
|
||||
a.remove();
|
||||
@ -298,10 +313,7 @@ export const applyEvent = async (event, socket) => {
|
||||
|
||||
// Send the event to the server.
|
||||
if (socket) {
|
||||
socket.emit(
|
||||
"event",
|
||||
JSON.stringify(event, (k, v) => (v === undefined ? null : v))
|
||||
);
|
||||
socket.emit("event", event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -329,7 +341,7 @@ export const applyRestEvent = async (event, socket) => {
|
||||
event.payload.files,
|
||||
event.payload.upload_id,
|
||||
event.payload.on_upload_progress,
|
||||
socket
|
||||
socket,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@ -396,7 +408,7 @@ export const connect = async (
|
||||
dispatch,
|
||||
transports,
|
||||
setConnectErrors,
|
||||
client_storage = {}
|
||||
client_storage = {},
|
||||
) => {
|
||||
// Get backend URL object from the endpoint.
|
||||
const endpoint = getBackendURL(EVENTURL);
|
||||
@ -405,8 +417,18 @@ export const connect = async (
|
||||
socket.current = io(endpoint.href, {
|
||||
path: endpoint["pathname"],
|
||||
transports: transports,
|
||||
protocols: [reflexEnvironment.version],
|
||||
autoUnref: false,
|
||||
});
|
||||
// Ensure undefined fields in events are sent as null instead of removed
|
||||
socket.current.io.encoder.replacer = (k, v) => (v === undefined ? null : v);
|
||||
socket.current.io.decoder.tryParse = (str) => {
|
||||
try {
|
||||
return JSON5.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
function checkVisibility() {
|
||||
if (document.visibilityState === "visible") {
|
||||
@ -443,8 +465,7 @@ export const connect = async (
|
||||
});
|
||||
|
||||
// On each received message, queue the updates and events.
|
||||
socket.current.on("event", async (message) => {
|
||||
const update = JSON5.parse(message);
|
||||
socket.current.on("event", async (update) => {
|
||||
for (const substate in update.delta) {
|
||||
dispatch[substate](update.delta[substate]);
|
||||
}
|
||||
@ -456,7 +477,7 @@ export const connect = async (
|
||||
});
|
||||
socket.current.on("reload", async (event) => {
|
||||
event_processing = false;
|
||||
queueEvents([...initialEvents(), JSON5.parse(event)], socket);
|
||||
queueEvents([...initialEvents(), event], socket);
|
||||
});
|
||||
|
||||
document.addEventListener("visibilitychange", checkVisibility);
|
||||
@ -478,14 +499,16 @@ export const uploadFiles = async (
|
||||
files,
|
||||
upload_id,
|
||||
on_upload_progress,
|
||||
socket
|
||||
socket,
|
||||
) => {
|
||||
// return if there's no file to upload
|
||||
if (files === undefined || files.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (upload_controllers[upload_id]) {
|
||||
const upload_ref_name = `__upload_controllers_${upload_id}`;
|
||||
|
||||
if (refs[upload_ref_name]) {
|
||||
console.log("Upload already in progress for ", upload_id);
|
||||
return false;
|
||||
}
|
||||
@ -497,23 +520,31 @@ export const uploadFiles = async (
|
||||
// Whenever called, responseText will contain the entire response so far.
|
||||
const chunks = progressEvent.event.target.responseText.trim().split("\n");
|
||||
// So only process _new_ chunks beyond resp_idx.
|
||||
chunks.slice(resp_idx).map((chunk) => {
|
||||
event_callbacks.map((f, ix) => {
|
||||
f(chunk)
|
||||
.then(() => {
|
||||
if (ix === event_callbacks.length - 1) {
|
||||
// Mark this chunk as processed.
|
||||
resp_idx += 1;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
if (progressEvent.progress === 1) {
|
||||
// Chunk may be incomplete, so only report errors when full response is available.
|
||||
console.log("Error parsing chunk", chunk, e);
|
||||
}
|
||||
return;
|
||||
});
|
||||
});
|
||||
chunks.slice(resp_idx).map((chunk_json) => {
|
||||
try {
|
||||
const chunk = JSON5.parse(chunk_json);
|
||||
event_callbacks.map((f, ix) => {
|
||||
f(chunk)
|
||||
.then(() => {
|
||||
if (ix === event_callbacks.length - 1) {
|
||||
// Mark this chunk as processed.
|
||||
resp_idx += 1;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
if (progressEvent.progress === 1) {
|
||||
// Chunk may be incomplete, so only report errors when full response is available.
|
||||
console.log("Error processing chunk", chunk, e);
|
||||
}
|
||||
return;
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
if (progressEvent.progress === 1) {
|
||||
console.log("Error parsing chunk", chunk_json, e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -537,7 +568,7 @@ export const uploadFiles = async (
|
||||
});
|
||||
|
||||
// Send the file to the server.
|
||||
upload_controllers[upload_id] = controller;
|
||||
refs[upload_ref_name] = controller;
|
||||
|
||||
try {
|
||||
return await axios.post(getBackendURL(UPLOADURL), formdata, config);
|
||||
@ -557,7 +588,7 @@ export const uploadFiles = async (
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
delete upload_controllers[upload_id];
|
||||
delete refs[upload_ref_name];
|
||||
}
|
||||
};
|
||||
|
||||
@ -573,7 +604,7 @@ export const Event = (
|
||||
name,
|
||||
payload = {},
|
||||
event_actions = {},
|
||||
handler = null
|
||||
handler = null,
|
||||
) => {
|
||||
return { name, payload, handler, event_actions };
|
||||
};
|
||||
@ -600,7 +631,7 @@ export const hydrateClientStorage = (client_storage) => {
|
||||
for (const state_key in client_storage.local_storage) {
|
||||
const options = client_storage.local_storage[state_key];
|
||||
const local_storage_value = localStorage.getItem(
|
||||
options.name || state_key
|
||||
options.name || state_key,
|
||||
);
|
||||
if (local_storage_value !== null) {
|
||||
client_storage_values[state_key] = local_storage_value;
|
||||
@ -611,7 +642,7 @@ export const hydrateClientStorage = (client_storage) => {
|
||||
for (const state_key in client_storage.session_storage) {
|
||||
const session_options = client_storage.session_storage[state_key];
|
||||
const session_storage_value = sessionStorage.getItem(
|
||||
session_options.name || state_key
|
||||
session_options.name || state_key,
|
||||
);
|
||||
if (session_storage_value != null) {
|
||||
client_storage_values[state_key] = session_storage_value;
|
||||
@ -636,7 +667,7 @@ export const hydrateClientStorage = (client_storage) => {
|
||||
const applyClientStorageDelta = (client_storage, delta) => {
|
||||
// find the main state and check for is_hydrated
|
||||
const unqualified_states = Object.keys(delta).filter(
|
||||
(key) => key.split(".").length === 1
|
||||
(key) => key.split(".").length === 1,
|
||||
);
|
||||
if (unqualified_states.length === 1) {
|
||||
const main_state = delta[unqualified_states[0]];
|
||||
@ -670,7 +701,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
|
||||
const session_options = client_storage.session_storage[state_key];
|
||||
sessionStorage.setItem(
|
||||
session_options.name || state_key,
|
||||
delta[substate][key]
|
||||
delta[substate][key],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -690,7 +721,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
|
||||
export const useEventLoop = (
|
||||
dispatch,
|
||||
initial_events = () => [],
|
||||
client_storage = {}
|
||||
client_storage = {},
|
||||
) => {
|
||||
const socket = useRef(null);
|
||||
const router = useRouter();
|
||||
@ -704,7 +735,7 @@ export const useEventLoop = (
|
||||
|
||||
event_actions = events.reduce(
|
||||
(acc, e) => ({ ...acc, ...e.event_actions }),
|
||||
event_actions ?? {}
|
||||
event_actions ?? {},
|
||||
);
|
||||
|
||||
const _e = args.filter((o) => o?.preventDefault !== undefined)[0];
|
||||
@ -732,7 +763,7 @@ export const useEventLoop = (
|
||||
debounce(
|
||||
combined_name,
|
||||
() => queueEvents(events, socket),
|
||||
event_actions.debounce
|
||||
event_actions.debounce,
|
||||
);
|
||||
} else {
|
||||
queueEvents(events, socket);
|
||||
@ -751,7 +782,7 @@ export const useEventLoop = (
|
||||
query,
|
||||
asPath,
|
||||
}))(router),
|
||||
}))
|
||||
})),
|
||||
);
|
||||
sentHydrate.current = true;
|
||||
}
|
||||
@ -786,31 +817,42 @@ export const useEventLoop = (
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Main event loop.
|
||||
// Handle socket connect/disconnect.
|
||||
useEffect(() => {
|
||||
// Skip if the router is not ready.
|
||||
if (!router.isReady) {
|
||||
return;
|
||||
}
|
||||
// only use websockets if state is present
|
||||
if (Object.keys(initialState).length > 1) {
|
||||
// only use websockets if state is present and backend is not disabled (reflex cloud).
|
||||
if (Object.keys(initialState).length > 1 && !isBackendDisabled()) {
|
||||
// Initialize the websocket connection.
|
||||
if (!socket.current) {
|
||||
connect(
|
||||
socket,
|
||||
dispatch,
|
||||
["websocket", "polling"],
|
||||
["websocket"],
|
||||
setConnectErrors,
|
||||
client_storage
|
||||
client_storage,
|
||||
);
|
||||
}
|
||||
(async () => {
|
||||
// Process all outstanding events.
|
||||
while (event_queue.length > 0 && !event_processing) {
|
||||
await processEvent(socket.current);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
// Cleanup function.
|
||||
return () => {
|
||||
if (socket.current) {
|
||||
socket.current.disconnect();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Main event loop.
|
||||
useEffect(() => {
|
||||
// Skip if the router is not ready.
|
||||
if (!router.isReady || isBackendDisabled()) {
|
||||
return;
|
||||
}
|
||||
(async () => {
|
||||
// Process all outstanding events.
|
||||
while (event_queue.length > 0 && !event_processing) {
|
||||
await processEvent(socket.current);
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
// localStorage event handling
|
||||
@ -834,7 +876,7 @@ export const useEventLoop = (
|
||||
vars[storage_to_state_map[e.key]] = e.newValue;
|
||||
const event = Event(
|
||||
`${state_name}.reflex___state____update_vars_internal_state.update_vars_internal`,
|
||||
{ vars: vars }
|
||||
{ vars: vars },
|
||||
);
|
||||
addEvents([event], e);
|
||||
}
|
||||
@ -927,7 +969,7 @@ export const getRefValues = (refs) => {
|
||||
return refs.map((ref) =>
|
||||
ref.current
|
||||
? ref.current.value || ref.current.getAttribute("aria-valuenow")
|
||||
: null
|
||||
: null,
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -84,6 +84,9 @@ In the example above, you will be able to do `rx.list`
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
|
||||
from reflex.utils import (
|
||||
compat, # for side-effects
|
||||
lazy_loader,
|
||||
@ -303,7 +306,6 @@ _MAPPING: dict = {
|
||||
"event": [
|
||||
"EventChain",
|
||||
"EventHandler",
|
||||
"background",
|
||||
"call_script",
|
||||
"call_function",
|
||||
"run_script",
|
||||
@ -331,7 +333,7 @@ _MAPPING: dict = {
|
||||
"SessionStorage",
|
||||
],
|
||||
"middleware": ["middleware", "Middleware"],
|
||||
"model": ["session", "Model"],
|
||||
"model": ["asession", "session", "Model"],
|
||||
"state": [
|
||||
"var",
|
||||
"ComponentState",
|
||||
@ -366,20 +368,5 @@ getattr, __dir__, __all__ = lazy_loader.attach(
|
||||
)
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "chakra":
|
||||
from reflex.utils import console
|
||||
|
||||
console.deprecate(
|
||||
"rx.chakra",
|
||||
reason="and moved to a separate package. "
|
||||
"To continue using Chakra UI components, install the `reflex-chakra` package via `pip install "
|
||||
"reflex-chakra`.",
|
||||
deprecation_version="0.6.0",
|
||||
removal_version="0.7.0",
|
||||
dedupe=True,
|
||||
)
|
||||
import reflex_chakra as rc
|
||||
|
||||
return rc
|
||||
def __getattr__(name: ModuleType | Any):
|
||||
return getattr(name)
|
||||
|
@ -131,7 +131,7 @@ from .components.radix.themes.layout.container import container as container
|
||||
from .components.radix.themes.layout.flex import flex as flex
|
||||
from .components.radix.themes.layout.grid import grid as grid
|
||||
from .components.radix.themes.layout.list import list_item as list_item
|
||||
from .components.radix.themes.layout.list import list_ns as list # noqa
|
||||
from .components.radix.themes.layout.list import list_ns as list # noqa: F401
|
||||
from .components.radix.themes.layout.list import ordered_list as ordered_list
|
||||
from .components.radix.themes.layout.list import unordered_list as unordered_list
|
||||
from .components.radix.themes.layout.section import section as section
|
||||
@ -156,7 +156,6 @@ from .constants import Env as Env
|
||||
from .constants.colors import Color as Color
|
||||
from .event import EventChain as EventChain
|
||||
from .event import EventHandler as EventHandler
|
||||
from .event import background as background
|
||||
from .event import call_function as call_function
|
||||
from .event import call_script as call_script
|
||||
from .event import clear_local_storage as clear_local_storage
|
||||
@ -186,6 +185,7 @@ from .istate.wrappers import get_state as get_state
|
||||
from .middleware import Middleware as Middleware
|
||||
from .middleware import middleware as middleware
|
||||
from .model import Model as Model
|
||||
from .model import asession as asession
|
||||
from .model import session as session
|
||||
from .page import page as page
|
||||
from .state import ComponentState as ComponentState
|
||||
|
418
reflex/app.py
418
reflex/app.py
@ -17,6 +17,7 @@ import sys
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
@ -26,6 +27,7 @@ from typing import (
|
||||
Dict,
|
||||
Generic,
|
||||
List,
|
||||
MutableMapping,
|
||||
Optional,
|
||||
Set,
|
||||
Type,
|
||||
@ -52,12 +54,17 @@ from reflex.compiler.compiler import ExecutorSafeFunctions, compile_theme
|
||||
from reflex.components.base.app_wrap import AppWrap
|
||||
from reflex.components.base.error_boundary import ErrorBoundary
|
||||
from reflex.components.base.fragment import Fragment
|
||||
from reflex.components.base.strict_mode import StrictMode
|
||||
from reflex.components.component import (
|
||||
Component,
|
||||
ComponentStyle,
|
||||
evaluate_style_namespaces,
|
||||
)
|
||||
from reflex.components.core.banner import connection_pulser, connection_toaster
|
||||
from reflex.components.core.banner import (
|
||||
backend_disabled,
|
||||
connection_pulser,
|
||||
connection_toaster,
|
||||
)
|
||||
from reflex.components.core.breakpoints import set_breakpoints
|
||||
from reflex.components.core.client_side_routing import (
|
||||
Default404Page,
|
||||
@ -67,6 +74,7 @@ from reflex.components.core.upload import Upload, get_upload_dir
|
||||
from reflex.components.radix import themes
|
||||
from reflex.config import environment, get_config
|
||||
from reflex.event import (
|
||||
_EVENT_FIELDS,
|
||||
BASE_STATE,
|
||||
Event,
|
||||
EventHandler,
|
||||
@ -143,7 +151,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
|
||||
position="top-center",
|
||||
id="backend_error",
|
||||
style={"width": "500px"},
|
||||
) # type: ignore
|
||||
)
|
||||
else:
|
||||
error_message.insert(0, "An error occurred.")
|
||||
return window_alert("\n".join(error_message))
|
||||
@ -155,9 +163,12 @@ def default_overlay_component() -> Component:
|
||||
Returns:
|
||||
The default overlay_component, which is a connection_modal.
|
||||
"""
|
||||
config = get_config()
|
||||
|
||||
return Fragment.create(
|
||||
connection_pulser(),
|
||||
connection_toaster(),
|
||||
*([backend_disabled()] if config.is_reflex_cloud else []),
|
||||
*codespaces.codespaces_auto_redirect(),
|
||||
)
|
||||
|
||||
@ -249,36 +260,36 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
# Attributes to add to the html root tag of every page.
|
||||
html_custom_attrs: Optional[Dict[str, str]] = None
|
||||
|
||||
# A map from a route to an unevaluated page. PRIVATE.
|
||||
unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
|
||||
# A map from a route to an unevaluated page.
|
||||
_unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
# A map from a page route to the component to render. Users should use `add_page`. PRIVATE.
|
||||
pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
|
||||
# A map from a page route to the component to render. Users should use `add_page`.
|
||||
_pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
|
||||
|
||||
# The backend API object. PRIVATE.
|
||||
api: FastAPI = None # type: ignore
|
||||
# The backend API object.
|
||||
_api: FastAPI | None = None
|
||||
|
||||
# The state class to use for the app. PRIVATE.
|
||||
state: Optional[Type[BaseState]] = None
|
||||
# The state class to use for the app.
|
||||
_state: Optional[Type[BaseState]] = None
|
||||
|
||||
# Class to manage many client states.
|
||||
_state_manager: Optional[StateManager] = None
|
||||
|
||||
# Mapping from a route to event handlers to trigger when the page loads. PRIVATE.
|
||||
load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
|
||||
# Mapping from a route to event handlers to trigger when the page loads.
|
||||
_load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
# Admin dashboard to view and manage the database. PRIVATE.
|
||||
# Admin dashboard to view and manage the database.
|
||||
admin_dash: Optional[AdminDash] = None
|
||||
|
||||
# The async server name space. PRIVATE.
|
||||
event_namespace: Optional[EventNamespace] = None
|
||||
# The async server name space.
|
||||
_event_namespace: Optional[EventNamespace] = None
|
||||
|
||||
# Background tasks that are currently running. PRIVATE.
|
||||
background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
|
||||
# Background tasks that are currently running.
|
||||
_background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
|
||||
|
||||
# Frontend Error Handler Function
|
||||
frontend_exception_handler: Callable[[Exception], None] = (
|
||||
@ -290,6 +301,24 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
[Exception], Union[EventSpec, List[EventSpec], None]
|
||||
] = default_backend_exception_handler
|
||||
|
||||
@property
|
||||
def api(self) -> FastAPI | None:
|
||||
"""Get the backend api.
|
||||
|
||||
Returns:
|
||||
The backend api.
|
||||
"""
|
||||
return self._api
|
||||
|
||||
@property
|
||||
def event_namespace(self) -> EventNamespace | None:
|
||||
"""Get the event namespace.
|
||||
|
||||
Returns:
|
||||
The event namespace.
|
||||
"""
|
||||
return self._event_namespace
|
||||
|
||||
def __post_init__(self):
|
||||
"""Initialize the app.
|
||||
|
||||
@ -309,7 +338,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
set_breakpoints(self.style.pop("breakpoints"))
|
||||
|
||||
# Set up the API.
|
||||
self.api = FastAPI(lifespan=self._run_lifespan_tasks)
|
||||
self._api = FastAPI(lifespan=self._run_lifespan_tasks)
|
||||
self._add_cors()
|
||||
self._add_default_endpoints()
|
||||
|
||||
@ -332,8 +361,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
def _enable_state(self) -> None:
|
||||
"""Enable state for the app."""
|
||||
if not self.state:
|
||||
self.state = State
|
||||
if not self._state:
|
||||
self._state = State
|
||||
self._setup_state()
|
||||
|
||||
def _setup_state(self) -> None:
|
||||
@ -342,13 +371,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
Raises:
|
||||
RuntimeError: If the socket server is invalid.
|
||||
"""
|
||||
if not self.state:
|
||||
if not self._state:
|
||||
return
|
||||
|
||||
config = get_config()
|
||||
|
||||
# Set up the state manager.
|
||||
self._state_manager = StateManager.create(state=self.state)
|
||||
self._state_manager = StateManager.create(state=self._state)
|
||||
|
||||
# Set up the Socket.IO AsyncServer.
|
||||
if not self.sio:
|
||||
@ -363,6 +392,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
max_http_buffer_size=constants.POLLING_MAX_HTTP_BUFFER_SIZE,
|
||||
ping_interval=constants.Ping.INTERVAL,
|
||||
ping_timeout=constants.Ping.TIMEOUT,
|
||||
json=SimpleNamespace(
|
||||
dumps=staticmethod(format.json_dumps),
|
||||
loads=staticmethod(json.loads),
|
||||
),
|
||||
transports=["websocket"],
|
||||
)
|
||||
elif getattr(self.sio, "async_mode", "") != "asgi":
|
||||
raise RuntimeError(
|
||||
@ -374,12 +408,42 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
namespace = config.get_event_namespace()
|
||||
|
||||
# Create the event namespace and attach the main app. Not related to any paths.
|
||||
self.event_namespace = EventNamespace(namespace, self)
|
||||
self._event_namespace = EventNamespace(namespace, self)
|
||||
|
||||
# Register the event namespace with the socket.
|
||||
self.sio.register_namespace(self.event_namespace)
|
||||
# Mount the socket app with the API.
|
||||
self.api.mount(str(constants.Endpoint.EVENT), socket_app)
|
||||
if self.api:
|
||||
|
||||
class HeaderMiddleware:
|
||||
def __init__(self, app: ASGIApp):
|
||||
self.app = app
|
||||
|
||||
async def __call__(
|
||||
self, scope: MutableMapping[str, Any], receive: Any, send: Callable
|
||||
):
|
||||
original_send = send
|
||||
|
||||
async def modified_send(message: dict):
|
||||
if message["type"] == "websocket.accept":
|
||||
if scope.get("subprotocols"):
|
||||
# The following *does* say "subprotocol" instead of "subprotocols", intentionally.
|
||||
message["subprotocol"] = scope["subprotocols"][0]
|
||||
|
||||
headers = dict(message.get("headers", []))
|
||||
header_key = b"sec-websocket-protocol"
|
||||
if subprotocol := headers.get(header_key):
|
||||
message["headers"] = [
|
||||
*message.get("headers", []),
|
||||
(header_key, subprotocol),
|
||||
]
|
||||
|
||||
return await original_send(message)
|
||||
|
||||
return await self.app(scope, receive, modified_send)
|
||||
|
||||
socket_app_with_headers = HeaderMiddleware(socket_app)
|
||||
self.api.mount(str(constants.Endpoint.EVENT), socket_app_with_headers)
|
||||
|
||||
# Check the exception handlers
|
||||
self._validate_exception_handlers()
|
||||
@ -390,24 +454,35 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
Returns:
|
||||
The string representation of the app.
|
||||
"""
|
||||
return f"<App state={self.state.__name__ if self.state else None}>"
|
||||
return f"<App state={self._state.__name__ if self._state else None}>"
|
||||
|
||||
def __call__(self) -> FastAPI:
|
||||
"""Run the backend api instance.
|
||||
|
||||
Raises:
|
||||
ValueError: If the app has not been initialized.
|
||||
|
||||
Returns:
|
||||
The backend api.
|
||||
"""
|
||||
if not self.api:
|
||||
raise ValueError("The app has not been initialized.")
|
||||
return self.api
|
||||
|
||||
def _add_default_endpoints(self):
|
||||
"""Add default api endpoints (ping)."""
|
||||
# To test the server.
|
||||
if not self.api:
|
||||
return
|
||||
|
||||
self.api.get(str(constants.Endpoint.PING))(ping)
|
||||
self.api.get(str(constants.Endpoint.HEALTH))(health)
|
||||
|
||||
def _add_optional_endpoints(self):
|
||||
"""Add optional api endpoints (_upload)."""
|
||||
if not self.api:
|
||||
return
|
||||
|
||||
if Upload.is_used:
|
||||
# To upload files.
|
||||
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
||||
@ -425,12 +500,14 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
def _add_cors(self):
|
||||
"""Add CORS middleware to the app."""
|
||||
if not self.api:
|
||||
return
|
||||
self.api.add_middleware(
|
||||
cors.CORSMiddleware,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
allow_origins=["*"],
|
||||
allow_origins=get_config().cors_allowed_origins,
|
||||
)
|
||||
|
||||
@property
|
||||
@ -456,18 +533,12 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
Returns:
|
||||
The generated component.
|
||||
|
||||
Raises:
|
||||
exceptions.MatchTypeError: If the return types of match cases in rx.match are different.
|
||||
"""
|
||||
try:
|
||||
return component if isinstance(component, Component) else component()
|
||||
except exceptions.MatchTypeError:
|
||||
raise
|
||||
return component if isinstance(component, Component) else component()
|
||||
|
||||
def add_page(
|
||||
self,
|
||||
component: Component | ComponentCallable,
|
||||
component: Component | ComponentCallable | None = None,
|
||||
route: str | None = None,
|
||||
title: str | Var | None = None,
|
||||
description: str | Var | None = None,
|
||||
@ -490,48 +561,64 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
meta: The metadata of the page.
|
||||
|
||||
Raises:
|
||||
ValueError: When the specified route name already exists.
|
||||
PageValueError: When the component is not set for a non-404 page.
|
||||
RouteValueError: When the specified route name already exists.
|
||||
"""
|
||||
# If the route is not set, get it from the callable.
|
||||
if route is None:
|
||||
if not isinstance(component, Callable):
|
||||
raise ValueError("Route must be set if component is not a callable.")
|
||||
raise exceptions.RouteValueError(
|
||||
"Route must be set if component is not a callable."
|
||||
)
|
||||
# Format the route.
|
||||
route = format.format_route(component.__name__)
|
||||
else:
|
||||
route = format.format_route(route, format_case=False)
|
||||
|
||||
if route == constants.Page404.SLUG:
|
||||
if component is None:
|
||||
component = Default404Page.create()
|
||||
component = wait_for_client_redirect(self._generate_component(component))
|
||||
title = title or constants.Page404.TITLE
|
||||
description = description or constants.Page404.DESCRIPTION
|
||||
image = image or constants.Page404.IMAGE
|
||||
else:
|
||||
if component is None:
|
||||
raise exceptions.PageValueError(
|
||||
"Component must be set for a non-404 page."
|
||||
)
|
||||
|
||||
# Check if the route given is valid
|
||||
verify_route_validity(route)
|
||||
|
||||
if route in self.unevaluated_pages and environment.RELOAD_CONFIG.is_set():
|
||||
if route in self._unevaluated_pages and environment.RELOAD_CONFIG.is_set():
|
||||
# when the app is reloaded(typically for app harness tests), we should maintain
|
||||
# the latest render function of a route.This applies typically to decorated pages
|
||||
# since they are only added when app._compile is called.
|
||||
self.unevaluated_pages.pop(route)
|
||||
self._unevaluated_pages.pop(route)
|
||||
|
||||
if route in self.unevaluated_pages:
|
||||
if route in self._unevaluated_pages:
|
||||
route_name = (
|
||||
f"`{route}` or `/`"
|
||||
if route == constants.PageNames.INDEX_ROUTE
|
||||
else f"`{route}`"
|
||||
)
|
||||
raise ValueError(
|
||||
raise exceptions.RouteValueError(
|
||||
f"Duplicate page route {route_name} already exists. Make sure you do not have two"
|
||||
f" pages with the same route"
|
||||
)
|
||||
|
||||
# Setup dynamic args for the route.
|
||||
# this state assignment is only required for tests using the deprecated state kwarg for App
|
||||
state = self.state if self.state else State
|
||||
state = self._state if self._state else State
|
||||
state.setup_dynamic_args(get_route_args(route))
|
||||
|
||||
if on_load:
|
||||
self.load_events[route] = (
|
||||
self._load_events[route] = (
|
||||
on_load if isinstance(on_load, list) else [on_load]
|
||||
)
|
||||
|
||||
self.unevaluated_pages[route] = UnevaluatedPage(
|
||||
self._unevaluated_pages[route] = UnevaluatedPage(
|
||||
component=component,
|
||||
route=route,
|
||||
title=title,
|
||||
@ -541,14 +628,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
def _compile_page(self, route: str):
|
||||
def _compile_page(self, route: str, save_page: bool = True):
|
||||
"""Compile a page.
|
||||
|
||||
Args:
|
||||
route: The route of the page to compile.
|
||||
save_page: If True, the compiled page is saved to self._pages.
|
||||
"""
|
||||
component, enable_state = compiler.compile_unevaluated_page(
|
||||
route, self.unevaluated_pages[route], self.state, self.style, self.theme
|
||||
route, self._unevaluated_pages[route], self._state, self.style, self.theme
|
||||
)
|
||||
|
||||
if enable_state:
|
||||
@ -556,7 +644,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
# Add the page.
|
||||
self._check_routes_conflict(route)
|
||||
self.pages[route] = component
|
||||
if save_page:
|
||||
self._pages[route] = component
|
||||
|
||||
def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
|
||||
"""Get the load events for a route.
|
||||
@ -570,7 +659,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
route = route.lstrip("/")
|
||||
if route == "":
|
||||
route = constants.PageNames.INDEX_ROUTE
|
||||
return self.load_events.get(route, [])
|
||||
return self._load_events.get(route, [])
|
||||
|
||||
def _check_routes_conflict(self, new_route: str):
|
||||
"""Verify if there is any conflict between the new route and any existing route.
|
||||
@ -594,10 +683,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
|
||||
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
|
||||
)
|
||||
for route in self.pages:
|
||||
for route in self._pages:
|
||||
replaced_route = replace_brackets_with_keywords(route)
|
||||
for rw, r, nr in zip(
|
||||
replaced_route.split("/"), route.split("/"), new_route.split("/")
|
||||
replaced_route.split("/"),
|
||||
route.split("/"),
|
||||
new_route.split("/"),
|
||||
strict=False,
|
||||
):
|
||||
if rw in segments and r != nr:
|
||||
# If the slugs in the segments of both routes are not the same, then the route is invalid
|
||||
@ -628,15 +720,19 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
Args:
|
||||
component: The component to display at the page.
|
||||
title: The title of the page.
|
||||
description: The description of the page.
|
||||
image: The image to display on the page.
|
||||
description: The description of the page.
|
||||
on_load: The event handler(s) that will be called each time the page load.
|
||||
meta: The metadata of the page.
|
||||
"""
|
||||
if component is None:
|
||||
component = Default404Page.create()
|
||||
console.deprecate(
|
||||
feature_name="App.add_custom_404_page",
|
||||
reason=f"Use app.add_page(component, route='/{constants.Page404.SLUG}') instead.",
|
||||
deprecation_version="0.6.7",
|
||||
removal_version="0.8.0",
|
||||
)
|
||||
self.add_page(
|
||||
component=wait_for_client_redirect(self._generate_component(component)),
|
||||
component=component,
|
||||
route=constants.Page404.SLUG,
|
||||
title=title or constants.Page404.TITLE,
|
||||
image=image or constants.Page404.IMAGE,
|
||||
@ -648,6 +744,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
def _setup_admin_dash(self):
|
||||
"""Setup the admin dash."""
|
||||
# Get the admin dash.
|
||||
if not self.api:
|
||||
return
|
||||
|
||||
admin_dash = self.admin_dash
|
||||
|
||||
if admin_dash and admin_dash.models:
|
||||
@ -689,7 +788,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
frontend_packages = get_config().frontend_packages
|
||||
_frontend_packages = []
|
||||
for package in frontend_packages:
|
||||
if package in (get_config().tailwind or {}).get("plugins", []): # type: ignore
|
||||
if package in (get_config().tailwind or {}).get("plugins", []):
|
||||
console.warn(
|
||||
f"Tailwind packages are inferred from 'plugins', remove `{package}` from `frontend_packages`"
|
||||
)
|
||||
@ -752,10 +851,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
def _setup_overlay_component(self):
|
||||
"""If a State is not used and no overlay_component is specified, do not render the connection modal."""
|
||||
if self.state is None and self.overlay_component is default_overlay_component:
|
||||
if self._state is None and self.overlay_component is default_overlay_component:
|
||||
self.overlay_component = None
|
||||
for k, component in self.pages.items():
|
||||
self.pages[k] = self._add_overlay_to_component(component)
|
||||
for k, component in self._pages.items():
|
||||
self._pages[k] = self._add_overlay_to_component(component)
|
||||
|
||||
def _add_error_boundary_to_component(self, component: Component) -> Component:
|
||||
if self.error_boundary is None:
|
||||
@ -767,14 +866,14 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
def _setup_error_boundary(self):
|
||||
"""If a State is not used and no error_boundary is specified, do not render the error boundary."""
|
||||
if self.state is None and self.error_boundary is default_error_boundary:
|
||||
if self._state is None and self.error_boundary is default_error_boundary:
|
||||
self.error_boundary = None
|
||||
|
||||
for k, component in self.pages.items():
|
||||
for k, component in self._pages.items():
|
||||
# Skip the 404 page
|
||||
if k == constants.Page404.SLUG:
|
||||
continue
|
||||
self.pages[k] = self._add_error_boundary_to_component(component)
|
||||
self._pages[k] = self._add_error_boundary_to_component(component)
|
||||
|
||||
def _apply_decorated_pages(self):
|
||||
"""Add @rx.page decorated pages to the app.
|
||||
@ -800,21 +899,27 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
Raises:
|
||||
VarDependencyError: When a computed var has an invalid dependency.
|
||||
"""
|
||||
if not self.state:
|
||||
if not self._state:
|
||||
return
|
||||
|
||||
if not state:
|
||||
state = self.state
|
||||
state = self._state
|
||||
|
||||
for var in state.computed_vars.values():
|
||||
if not var._cache:
|
||||
continue
|
||||
deps = var._deps(objclass=state)
|
||||
for dep in deps:
|
||||
if dep not in state.vars and dep not in state.backend_vars:
|
||||
raise exceptions.VarDependencyError(
|
||||
f"ComputedVar {var._js_expr} on state {state.__name__} has an invalid dependency {dep}"
|
||||
)
|
||||
for state_name, dep_set in deps.items():
|
||||
state_cls = (
|
||||
state.get_root_state().get_class_substate(state_name)
|
||||
if state_name != state.get_full_name()
|
||||
else state
|
||||
)
|
||||
for dep in dep_set:
|
||||
if dep not in state_cls.vars and dep not in state_cls.backend_vars:
|
||||
raise exceptions.VarDependencyError(
|
||||
f"ComputedVar {var._js_expr} on state {state.__name__} has an invalid dependency {state_name}.{dep}"
|
||||
)
|
||||
|
||||
for substate in state.class_subclasses:
|
||||
self._validate_var_dependencies(substate)
|
||||
@ -830,14 +935,14 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
"""
|
||||
from reflex.utils.exceptions import ReflexRuntimeError
|
||||
|
||||
self.pages = {}
|
||||
self._pages = {}
|
||||
|
||||
def get_compilation_time() -> str:
|
||||
return str(datetime.now().time()).split(".")[0]
|
||||
|
||||
# Render a default 404 page if the user didn't supply one
|
||||
if constants.Page404.SLUG not in self.unevaluated_pages:
|
||||
self.add_custom_404_page()
|
||||
if constants.Page404.SLUG not in self._unevaluated_pages:
|
||||
self.add_page(route=constants.Page404.SLUG)
|
||||
|
||||
# Fix up the style.
|
||||
self.style = evaluate_style_namespaces(self.style)
|
||||
@ -852,20 +957,24 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
# If a theme component was provided, wrap the app with it
|
||||
app_wrappers[(20, "Theme")] = self.theme
|
||||
|
||||
for route in self.unevaluated_pages:
|
||||
console.debug(f"Evaluating page: {route}")
|
||||
self._compile_page(route)
|
||||
# Get the env mode.
|
||||
config = get_config()
|
||||
|
||||
# Add the optional endpoints (_upload)
|
||||
self._add_optional_endpoints()
|
||||
if config.react_strict_mode:
|
||||
app_wrappers[(200, "StrictMode")] = StrictMode.create()
|
||||
|
||||
should_compile = self._should_compile()
|
||||
|
||||
if not should_compile:
|
||||
for route in self._unevaluated_pages:
|
||||
console.debug(f"Evaluating page: {route}")
|
||||
self._compile_page(route, save_page=should_compile)
|
||||
|
||||
# Add the optional endpoints (_upload)
|
||||
self._add_optional_endpoints()
|
||||
|
||||
if not self._should_compile():
|
||||
return
|
||||
|
||||
self._validate_var_dependencies()
|
||||
self._setup_overlay_component()
|
||||
self._setup_error_boundary()
|
||||
|
||||
# Create a progress bar.
|
||||
progress = Progress(
|
||||
*Progress.get_default_columns()[:-1],
|
||||
@ -874,18 +983,30 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
)
|
||||
|
||||
# try to be somewhat accurate - but still not 100%
|
||||
adhoc_steps_without_executor = 6
|
||||
adhoc_steps_without_executor = 7
|
||||
fixed_pages_within_executor = 5
|
||||
progress.start()
|
||||
task = progress.add_task(
|
||||
f"[{get_compilation_time()}] Compiling:",
|
||||
total=len(self.pages)
|
||||
total=len(self._pages)
|
||||
+ (len(self._unevaluated_pages) * 2)
|
||||
+ fixed_pages_within_executor
|
||||
+ adhoc_steps_without_executor,
|
||||
)
|
||||
|
||||
# Get the env mode.
|
||||
config = get_config()
|
||||
for route in self._unevaluated_pages:
|
||||
console.debug(f"Evaluating page: {route}")
|
||||
self._compile_page(route, save_page=should_compile)
|
||||
progress.advance(task)
|
||||
|
||||
# Add the optional endpoints (_upload)
|
||||
self._add_optional_endpoints()
|
||||
|
||||
self._validate_var_dependencies()
|
||||
self._setup_overlay_component()
|
||||
self._setup_error_boundary()
|
||||
|
||||
progress.advance(task)
|
||||
|
||||
# Store the compile results.
|
||||
compile_results = []
|
||||
@ -898,7 +1019,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
# This has to happen before compiling stateful components as that
|
||||
# prevents recursive functions from reaching all components.
|
||||
for component in self.pages.values():
|
||||
for component in self._pages.values():
|
||||
# Add component._get_all_imports() to all_imports.
|
||||
all_imports.update(component._get_all_imports())
|
||||
|
||||
@ -913,12 +1034,12 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
stateful_components_path,
|
||||
stateful_components_code,
|
||||
page_components,
|
||||
) = compiler.compile_stateful_components(self.pages.values())
|
||||
) = compiler.compile_stateful_components(self._pages.values())
|
||||
|
||||
progress.advance(task)
|
||||
|
||||
# Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
|
||||
if code_uses_state_contexts(stateful_components_code) and self.state is None:
|
||||
if code_uses_state_contexts(stateful_components_code) and self._state is None:
|
||||
raise ReflexRuntimeError(
|
||||
"To access rx.State in frontend components, at least one "
|
||||
"subclass of rx.State must be defined in the app."
|
||||
@ -932,7 +1053,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
compiler.compile_document_root(
|
||||
self.head_components,
|
||||
html_lang=self.html_lang,
|
||||
html_custom_attrs=self.html_custom_attrs, # type: ignore
|
||||
html_custom_attrs=self.html_custom_attrs, # pyright: ignore [reportArgumentType]
|
||||
)
|
||||
)
|
||||
|
||||
@ -947,29 +1068,28 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
is not None
|
||||
):
|
||||
executor = concurrent.futures.ProcessPoolExecutor(
|
||||
max_workers=number_of_processes,
|
||||
max_workers=number_of_processes or None,
|
||||
mp_context=multiprocessing.get_context("fork"),
|
||||
)
|
||||
else:
|
||||
executor = concurrent.futures.ThreadPoolExecutor(
|
||||
max_workers=environment.REFLEX_COMPILE_THREADS.get()
|
||||
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
|
||||
)
|
||||
|
||||
for route, component in zip(self.pages, page_components):
|
||||
for route, component in zip(self._pages, page_components, strict=True):
|
||||
ExecutorSafeFunctions.COMPONENTS[route] = component
|
||||
|
||||
ExecutorSafeFunctions.STATE = self.state
|
||||
ExecutorSafeFunctions.STATE = self._state
|
||||
|
||||
with executor:
|
||||
result_futures = []
|
||||
|
||||
def _submit_work(fn, *args, **kwargs):
|
||||
def _submit_work(fn: Callable, *args, **kwargs):
|
||||
f = executor.submit(fn, *args, **kwargs)
|
||||
# f = executor.apipe(fn, *args, **kwargs)
|
||||
result_futures.append(f)
|
||||
|
||||
# Compile the pre-compiled pages.
|
||||
for route in self.pages:
|
||||
for route in self._pages:
|
||||
_submit_work(
|
||||
ExecutorSafeFunctions.compile_page,
|
||||
route,
|
||||
@ -1004,7 +1124,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
# Compile the contexts.
|
||||
compile_results.append(
|
||||
compiler.compile_contexts(self.state, self.theme),
|
||||
compiler.compile_contexts(self._state, self.theme),
|
||||
)
|
||||
if self.theme is not None:
|
||||
# Fix #2992 by removing the top-level appearance prop
|
||||
@ -1126,9 +1246,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
)
|
||||
|
||||
task = asyncio.create_task(_coro())
|
||||
self.background_tasks.add(task)
|
||||
self._background_tasks.add(task)
|
||||
# Clean up task from background_tasks set when complete.
|
||||
task.add_done_callback(self.background_tasks.discard)
|
||||
task.add_done_callback(self._background_tasks.discard)
|
||||
return task
|
||||
|
||||
def _validate_exception_handlers(self):
|
||||
@ -1138,11 +1258,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
ValueError: If the custom exception handlers are invalid.
|
||||
|
||||
"""
|
||||
FRONTEND_ARG_SPEC = {
|
||||
frontend_arg_spec = {
|
||||
"exception": Exception,
|
||||
}
|
||||
|
||||
BACKEND_ARG_SPEC = {
|
||||
backend_arg_spec = {
|
||||
"exception": Exception,
|
||||
}
|
||||
|
||||
@ -1150,14 +1270,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
["frontend", "backend"],
|
||||
[self.frontend_exception_handler, self.backend_exception_handler],
|
||||
[
|
||||
FRONTEND_ARG_SPEC,
|
||||
BACKEND_ARG_SPEC,
|
||||
frontend_arg_spec,
|
||||
backend_arg_spec,
|
||||
],
|
||||
strict=True,
|
||||
):
|
||||
if hasattr(handler_fn, "__name__"):
|
||||
_fn_name = handler_fn.__name__
|
||||
else:
|
||||
_fn_name = handler_fn.__class__.__name__
|
||||
_fn_name = type(handler_fn).__name__
|
||||
|
||||
if isinstance(handler_fn, functools.partial):
|
||||
raise ValueError(
|
||||
@ -1193,7 +1314,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
):
|
||||
raise ValueError(
|
||||
f"Provided custom {handler_domain} exception handler `{_fn_name}` has the wrong argument order."
|
||||
f"Expected `{required_arg}` as the {required_arg_index+1} argument but got `{list(arg_annotations.keys())[required_arg_index]}`"
|
||||
f"Expected `{required_arg}` as the {required_arg_index + 1} argument but got `{list(arg_annotations.keys())[required_arg_index]}`"
|
||||
)
|
||||
|
||||
if not issubclass(arg_annotations[required_arg], Exception):
|
||||
@ -1270,7 +1391,7 @@ async def process(
|
||||
await asyncio.create_task(
|
||||
app.event_namespace.emit(
|
||||
"reload",
|
||||
data=format.json_dumps(event),
|
||||
data=event,
|
||||
to=sid,
|
||||
)
|
||||
)
|
||||
@ -1294,15 +1415,14 @@ async def process(
|
||||
if app._process_background(state, event) is not None:
|
||||
# `final=True` allows the frontend send more events immediately.
|
||||
yield StateUpdate(final=True)
|
||||
return
|
||||
else:
|
||||
# Process the event synchronously.
|
||||
async for update in state._process(event):
|
||||
# Postprocess the event.
|
||||
update = await app._postprocess(state, event, update)
|
||||
|
||||
# Process the event synchronously.
|
||||
async for update in state._process(event):
|
||||
# Postprocess the event.
|
||||
update = await app._postprocess(state, event, update)
|
||||
|
||||
# Yield the update.
|
||||
yield update
|
||||
# Yield the update.
|
||||
yield update
|
||||
except Exception as ex:
|
||||
telemetry.send_error(ex, context="backend")
|
||||
|
||||
@ -1331,20 +1451,22 @@ async def health() -> JSONResponse:
|
||||
health_status = {"status": True}
|
||||
status_code = 200
|
||||
|
||||
db_status, redis_status = await asyncio.gather(
|
||||
get_db_status(), prerequisites.get_redis_status()
|
||||
)
|
||||
tasks = []
|
||||
|
||||
health_status["db"] = db_status
|
||||
if prerequisites.check_db_used():
|
||||
tasks.append(get_db_status())
|
||||
if prerequisites.check_redis_used():
|
||||
tasks.append(prerequisites.get_redis_status())
|
||||
|
||||
if redis_status is None:
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
for result in results:
|
||||
health_status |= result
|
||||
|
||||
if "redis" in health_status and health_status["redis"] is None:
|
||||
health_status["redis"] = False
|
||||
else:
|
||||
health_status["redis"] = redis_status
|
||||
|
||||
if not health_status["db"] or (
|
||||
not health_status["redis"] and redis_status is not None
|
||||
):
|
||||
if not all(health_status.values()):
|
||||
health_status["status"] = False
|
||||
status_code = 503
|
||||
|
||||
@ -1495,16 +1617,20 @@ class EventNamespace(AsyncNamespace):
|
||||
self.sid_to_token = {}
|
||||
self.app = app
|
||||
|
||||
def on_connect(self, sid, environ):
|
||||
def on_connect(self, sid: str, environ: dict):
|
||||
"""Event for when the websocket is connected.
|
||||
|
||||
Args:
|
||||
sid: The Socket.IO session id.
|
||||
environ: The request information, including HTTP headers.
|
||||
"""
|
||||
pass
|
||||
subprotocol = environ.get("HTTP_SEC_WEBSOCKET_PROTOCOL")
|
||||
if subprotocol and subprotocol != constants.Reflex.VERSION:
|
||||
console.warn(
|
||||
f"Frontend version {subprotocol} for session {sid} does not match the backend version {constants.Reflex.VERSION}."
|
||||
)
|
||||
|
||||
def on_disconnect(self, sid):
|
||||
def on_disconnect(self, sid: str):
|
||||
"""Event for when the websocket disconnects.
|
||||
|
||||
Args:
|
||||
@ -1523,10 +1649,10 @@ class EventNamespace(AsyncNamespace):
|
||||
"""
|
||||
# Creating a task prevents the update from being blocked behind other coroutines.
|
||||
await asyncio.create_task(
|
||||
self.emit(str(constants.SocketEvent.EVENT), update.json(), to=sid)
|
||||
self.emit(str(constants.SocketEvent.EVENT), update, to=sid)
|
||||
)
|
||||
|
||||
async def on_event(self, sid, data):
|
||||
async def on_event(self, sid: str, data: Any):
|
||||
"""Event for receiving front-end websocket events.
|
||||
|
||||
Raises:
|
||||
@ -1535,12 +1661,36 @@ class EventNamespace(AsyncNamespace):
|
||||
Args:
|
||||
sid: The Socket.IO session id.
|
||||
data: The event data.
|
||||
|
||||
Raises:
|
||||
EventDeserializationError: If the event data is not a dictionary.
|
||||
"""
|
||||
fields = json.loads(data)
|
||||
# Get the event.
|
||||
event = Event(
|
||||
**{k: v for k, v in fields.items() if k not in ("handler", "event_actions")}
|
||||
)
|
||||
fields = data
|
||||
|
||||
if isinstance(fields, str):
|
||||
console.warn(
|
||||
"Received event data as a string. This generally should not happen and may indicate a bug."
|
||||
f" Event data: {fields}"
|
||||
)
|
||||
try:
|
||||
fields = json.loads(fields)
|
||||
except json.JSONDecodeError as ex:
|
||||
raise exceptions.EventDeserializationError(
|
||||
f"Failed to deserialize event data: {fields}."
|
||||
) from ex
|
||||
|
||||
if not isinstance(fields, dict):
|
||||
raise exceptions.EventDeserializationError(
|
||||
f"Event data must be a dictionary, but received {fields} of type {type(fields)}."
|
||||
)
|
||||
|
||||
try:
|
||||
# Get the event.
|
||||
event = Event(**{k: v for k, v in fields.items() if k in _EVENT_FIELDS})
|
||||
except (TypeError, ValueError) as ex:
|
||||
raise exceptions.EventDeserializationError(
|
||||
f"Failed to deserialize event data: {fields}."
|
||||
) from ex
|
||||
|
||||
self.token_to_sid[event.token] = sid
|
||||
self.sid_to_token[sid] = event.token
|
||||
@ -1569,7 +1719,7 @@ class EventNamespace(AsyncNamespace):
|
||||
# Emit the update from processing the event.
|
||||
await self.emit_update(update=update, sid=sid)
|
||||
|
||||
async def on_ping(self, sid):
|
||||
async def on_ping(self, sid: str):
|
||||
"""Event for testing the API endpoint.
|
||||
|
||||
Args:
|
||||
|
@ -12,7 +12,7 @@ from typing import Callable, Coroutine, Set, Union
|
||||
from fastapi import FastAPI
|
||||
|
||||
from reflex.utils import console
|
||||
from reflex.utils.exceptions import InvalidLifespanTaskType
|
||||
from reflex.utils.exceptions import InvalidLifespanTaskTypeError
|
||||
|
||||
from .mixin import AppMixin
|
||||
|
||||
@ -32,7 +32,7 @@ class LifespanMixin(AppMixin):
|
||||
try:
|
||||
async with contextlib.AsyncExitStack() as stack:
|
||||
for task in self.lifespan_tasks:
|
||||
run_msg = f"Started lifespan task: {task.__name__} as {{type}}" # type: ignore
|
||||
run_msg = f"Started lifespan task: {task.__name__} as {{type}}" # pyright: ignore [reportAttributeAccessIssue]
|
||||
if isinstance(task, asyncio.Task):
|
||||
running_tasks.append(task)
|
||||
else:
|
||||
@ -61,19 +61,19 @@ class LifespanMixin(AppMixin):
|
||||
|
||||
Args:
|
||||
task: The task to register.
|
||||
task_kwargs: The kwargs of the task.
|
||||
**task_kwargs: The kwargs of the task.
|
||||
|
||||
Raises:
|
||||
InvalidLifespanTaskType: If the task is a generator function.
|
||||
InvalidLifespanTaskTypeError: If the task is a generator function.
|
||||
"""
|
||||
if inspect.isgeneratorfunction(task) or inspect.isasyncgenfunction(task):
|
||||
raise InvalidLifespanTaskType(
|
||||
raise InvalidLifespanTaskTypeError(
|
||||
f"Task {task.__name__} of type generator must be decorated with contextlib.asynccontextmanager."
|
||||
)
|
||||
|
||||
if task_kwargs:
|
||||
original_task = task
|
||||
task = functools.partial(task, **task_kwargs) # type: ignore
|
||||
functools.update_wrapper(task, original_task) # type: ignore
|
||||
self.lifespan_tasks.add(task) # type: ignore
|
||||
console.debug(f"Registered lifespan task: {task.__name__}") # type: ignore
|
||||
task = functools.partial(task, **task_kwargs) # pyright: ignore [reportArgumentType]
|
||||
functools.update_wrapper(task, original_task) # pyright: ignore [reportArgumentType]
|
||||
self.lifespan_tasks.add(task)
|
||||
console.debug(f"Registered lifespan task: {task.__name__}") # pyright: ignore [reportAttributeAccessIssue]
|
||||
|
@ -53,11 +53,11 @@ class MiddlewareMixin(AppMixin):
|
||||
"""
|
||||
for middleware in self.middleware:
|
||||
if asyncio.iscoroutinefunction(middleware.preprocess):
|
||||
out = await middleware.preprocess(app=self, state=state, event=event) # type: ignore
|
||||
out = await middleware.preprocess(app=self, state=state, event=event) # pyright: ignore [reportArgumentType]
|
||||
else:
|
||||
out = middleware.preprocess(app=self, state=state, event=event) # type: ignore
|
||||
out = middleware.preprocess(app=self, state=state, event=event) # pyright: ignore [reportArgumentType]
|
||||
if out is not None:
|
||||
return out # type: ignore
|
||||
return out # pyright: ignore [reportReturnType]
|
||||
|
||||
async def _postprocess(
|
||||
self, state: BaseState, event: Event, update: StateUpdate
|
||||
@ -78,18 +78,18 @@ class MiddlewareMixin(AppMixin):
|
||||
for middleware in self.middleware:
|
||||
if asyncio.iscoroutinefunction(middleware.postprocess):
|
||||
out = await middleware.postprocess(
|
||||
app=self, # type: ignore
|
||||
app=self, # pyright: ignore [reportArgumentType]
|
||||
state=state,
|
||||
event=event,
|
||||
update=update,
|
||||
)
|
||||
else:
|
||||
out = middleware.postprocess(
|
||||
app=self, # type: ignore
|
||||
app=self, # pyright: ignore [reportArgumentType]
|
||||
state=state,
|
||||
event=event,
|
||||
update=update,
|
||||
)
|
||||
if out is not None:
|
||||
return out # type: ignore
|
||||
return out # pyright: ignore [reportReturnType]
|
||||
return update
|
||||
|
@ -5,16 +5,13 @@ Only the app attribute is explicitly exposed.
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from reflex import constants
|
||||
from reflex.utils import telemetry
|
||||
from reflex.utils.exec import is_prod_mode
|
||||
from reflex.utils.prerequisites import get_app
|
||||
from reflex.utils.prerequisites import get_and_validate_app
|
||||
|
||||
if constants.CompileVars.APP != "app":
|
||||
raise AssertionError("unexpected variable name for 'app'")
|
||||
|
||||
telemetry.send("compile")
|
||||
app_module = get_app(reload=False)
|
||||
app = getattr(app_module, constants.CompileVars.APP)
|
||||
app, app_module = get_and_validate_app(reload=False)
|
||||
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
|
||||
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
||||
app._apply_decorated_pages()
|
||||
@ -30,8 +27,7 @@ if is_prod_mode():
|
||||
# ensure only "app" is exposed.
|
||||
del app_module
|
||||
del compile_future
|
||||
del get_app
|
||||
del get_and_validate_app
|
||||
del is_prod_mode
|
||||
del telemetry
|
||||
del constants
|
||||
del ThreadPoolExecutor
|
||||
|
@ -5,7 +5,7 @@ from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from reflex import constants
|
||||
from reflex.utils.exec import is_backend_only
|
||||
from reflex.config import EnvironmentVariables
|
||||
|
||||
|
||||
def asset(
|
||||
@ -52,7 +52,7 @@ def asset(
|
||||
The relative URL to the asset.
|
||||
"""
|
||||
assets = constants.Dirs.APP_ASSETS
|
||||
backend_only = is_backend_only()
|
||||
backend_only = EnvironmentVariables.REFLEX_BACKEND_ONLY.get()
|
||||
|
||||
# Local asset handling
|
||||
if not shared:
|
||||
|
@ -13,7 +13,7 @@ except ModuleNotFoundError:
|
||||
if not TYPE_CHECKING:
|
||||
import pydantic.main as pydantic_main
|
||||
from pydantic import BaseModel
|
||||
from pydantic.fields import ModelField # type: ignore
|
||||
from pydantic.fields import ModelField
|
||||
|
||||
|
||||
def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None:
|
||||
@ -30,26 +30,27 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None
|
||||
|
||||
# can't use reflex.config.environment here cause of circular import
|
||||
reload = os.getenv("__RELOAD_CONFIG", "").lower() == "true"
|
||||
for base in bases:
|
||||
try:
|
||||
base = None
|
||||
try:
|
||||
for base in bases:
|
||||
if not reload and getattr(base, field_name, None):
|
||||
pass
|
||||
except TypeError as te:
|
||||
raise VarNameError(
|
||||
f'State var "{field_name}" in {base} has been shadowed by a substate var; '
|
||||
f'use a different field name instead".'
|
||||
) from te
|
||||
except TypeError as te:
|
||||
raise VarNameError(
|
||||
f'State var "{field_name}" in {base} has been shadowed by a substate var; '
|
||||
f'use a different field name instead".'
|
||||
) from te
|
||||
|
||||
|
||||
# monkeypatch pydantic validate_field_name method to skip validating
|
||||
# shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True)
|
||||
pydantic_main.validate_field_name = validate_field_name # type: ignore
|
||||
pydantic_main.validate_field_name = validate_field_name # pyright: ignore [reportPossiblyUnboundVariable, reportPrivateImportUsage]
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from reflex.vars import Var
|
||||
|
||||
|
||||
class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
||||
class Base(BaseModel): # pyright: ignore [reportPossiblyUnboundVariable]
|
||||
"""The base class subclassed by all Reflex classes.
|
||||
|
||||
This class wraps Pydantic and provides common methods such as
|
||||
@ -74,12 +75,12 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
||||
"""
|
||||
from reflex.utils.serializers import serialize
|
||||
|
||||
return self.__config__.json_dumps( # type: ignore
|
||||
return self.__config__.json_dumps(
|
||||
self.dict(),
|
||||
default=serialize,
|
||||
)
|
||||
|
||||
def set(self, **kwargs):
|
||||
def set(self, **kwargs: Any):
|
||||
"""Set multiple fields and return the object.
|
||||
|
||||
Args:
|
||||
@ -112,12 +113,12 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
||||
default_value: The default value of the field
|
||||
"""
|
||||
var_name = var._var_field_name
|
||||
new_field = ModelField.infer(
|
||||
new_field = ModelField.infer( # pyright: ignore [reportPossiblyUnboundVariable]
|
||||
name=var_name,
|
||||
value=default_value,
|
||||
annotation=var._var_type,
|
||||
class_validators=None,
|
||||
config=cls.__config__, # type: ignore
|
||||
config=cls.__config__,
|
||||
)
|
||||
cls.__fields__.update({var_name: new_field})
|
||||
|
||||
|
@ -75,7 +75,7 @@ def _compile_app(app_root: Component) -> str:
|
||||
return templates.APP_ROOT.render(
|
||||
imports=utils.compile_imports(app_root._get_all_imports()),
|
||||
custom_codes=app_root._get_all_custom_code(),
|
||||
hooks={**app_root._get_all_hooks_internal(), **app_root._get_all_hooks()},
|
||||
hooks=app_root._get_all_hooks(),
|
||||
window_libraries=window_libraries,
|
||||
render=app_root.render(),
|
||||
)
|
||||
@ -149,7 +149,7 @@ def _compile_page(
|
||||
imports=imports,
|
||||
dynamic_imports=component._get_all_dynamic_imports(),
|
||||
custom_codes=component._get_all_custom_code(),
|
||||
hooks={**component._get_all_hooks_internal(), **component._get_all_hooks()},
|
||||
hooks=component._get_all_hooks(),
|
||||
render=component.render(),
|
||||
**kwargs,
|
||||
)
|
||||
@ -239,11 +239,19 @@ def _compile_components(
|
||||
component_renders.append(component_render)
|
||||
imports = utils.merge_imports(imports, component_imports)
|
||||
|
||||
dynamic_imports = {
|
||||
comp_import: None
|
||||
for comp_render in component_renders
|
||||
if "dynamic_imports" in comp_render
|
||||
for comp_import in comp_render["dynamic_imports"]
|
||||
}
|
||||
|
||||
# Compile the components page.
|
||||
return (
|
||||
templates.COMPONENTS.render(
|
||||
imports=utils.compile_imports(imports),
|
||||
components=component_renders,
|
||||
dynamic_imports=dynamic_imports,
|
||||
),
|
||||
imports,
|
||||
)
|
||||
|
@ -1,9 +1,46 @@
|
||||
"""Templates to use in the reflex compiler."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader, Template
|
||||
|
||||
from reflex import constants
|
||||
from reflex.constants import Hooks
|
||||
from reflex.utils.format import format_state_name, json_dumps
|
||||
from reflex.vars.base import VarData
|
||||
|
||||
|
||||
def _sort_hooks(hooks: dict[str, VarData | None]):
|
||||
"""Sort the hooks by their position.
|
||||
|
||||
Args:
|
||||
hooks: The hooks to sort.
|
||||
|
||||
Returns:
|
||||
The sorted hooks.
|
||||
"""
|
||||
sorted_hooks = {
|
||||
Hooks.HookPosition.INTERNAL: [],
|
||||
Hooks.HookPosition.PRE_TRIGGER: [],
|
||||
Hooks.HookPosition.POST_TRIGGER: [],
|
||||
}
|
||||
|
||||
for hook, data in hooks.items():
|
||||
if data and data.position and data.position == Hooks.HookPosition.INTERNAL:
|
||||
sorted_hooks[Hooks.HookPosition.INTERNAL].append((hook, data))
|
||||
elif not data or (
|
||||
not data.position
|
||||
or data.position == constants.Hooks.HookPosition.PRE_TRIGGER
|
||||
):
|
||||
sorted_hooks[Hooks.HookPosition.PRE_TRIGGER].append((hook, data))
|
||||
elif (
|
||||
data
|
||||
and data.position
|
||||
and data.position == constants.Hooks.HookPosition.POST_TRIGGER
|
||||
):
|
||||
sorted_hooks[Hooks.HookPosition.POST_TRIGGER].append((hook, data))
|
||||
|
||||
return sorted_hooks
|
||||
|
||||
|
||||
class ReflexJinjaEnvironment(Environment):
|
||||
@ -45,7 +82,9 @@ class ReflexJinjaEnvironment(Environment):
|
||||
"on_load_internal": constants.CompileVars.ON_LOAD_INTERNAL,
|
||||
"update_vars_internal": constants.CompileVars.UPDATE_VARS_INTERNAL,
|
||||
"frontend_exception_state": constants.CompileVars.FRONTEND_EXCEPTION_STATE_FULL,
|
||||
"hook_position": constants.Hooks.HookPosition,
|
||||
}
|
||||
self.globals["sort_hooks"] = _sort_hooks
|
||||
|
||||
|
||||
def get_template(name: str) -> Template:
|
||||
@ -102,6 +141,9 @@ STYLE = get_template("web/styles/styles.css.jinja2")
|
||||
# Code that generate the package json file
|
||||
PACKAGE_JSON = get_template("web/package.json.jinja2")
|
||||
|
||||
# Template containing some macros used in the web pages.
|
||||
MACROS = get_template("web/pages/macros.js.jinja2")
|
||||
|
||||
# Code that generate the pyproject.toml file for custom components.
|
||||
CUSTOM_COMPONENTS_PYPROJECT_TOML = get_template(
|
||||
"custom_components/pyproject.toml.jinja2"
|
||||
|
@ -2,17 +2,24 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, Optional, Type, Union
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from reflex.utils.exec import is_in_app_harness
|
||||
from reflex.utils.prerequisites import get_web_dir
|
||||
from reflex.vars.base import Var
|
||||
|
||||
try:
|
||||
from pydantic.v1.fields import ModelField
|
||||
except ModuleNotFoundError:
|
||||
from pydantic.fields import ModelField # type: ignore
|
||||
from pydantic.fields import (
|
||||
ModelField, # pyright: ignore [reportAttributeAccessIssue]
|
||||
)
|
||||
|
||||
from reflex import constants
|
||||
from reflex.components.base import (
|
||||
@ -29,7 +36,7 @@ from reflex.components.base import (
|
||||
)
|
||||
from reflex.components.component import Component, ComponentStyle, CustomComponent
|
||||
from reflex.istate.storage import Cookie, LocalStorage, SessionStorage
|
||||
from reflex.state import BaseState
|
||||
from reflex.state import BaseState, _resolve_delta
|
||||
from reflex.style import Style
|
||||
from reflex.utils import console, format, imports, path_ops
|
||||
from reflex.utils.imports import ImportVar, ParsedImportDict
|
||||
@ -115,7 +122,7 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]:
|
||||
default, rest = compile_import_statement(fields)
|
||||
|
||||
# prevent lib from being rendered on the page if all imports are non rendered kind
|
||||
if not any({f.render for f in fields}): # type: ignore
|
||||
if not any(f.render for f in fields):
|
||||
continue
|
||||
|
||||
if not lib:
|
||||
@ -123,8 +130,7 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]:
|
||||
raise ValueError("No default field allowed for empty library.")
|
||||
if rest is None or len(rest) == 0:
|
||||
raise ValueError("No fields to import.")
|
||||
for module in sorted(rest):
|
||||
import_dicts.append(get_import_dict(module))
|
||||
import_dicts.extend(get_import_dict(module) for module in sorted(rest))
|
||||
continue
|
||||
|
||||
# remove the version before rendering the package imports
|
||||
@ -164,13 +170,34 @@ def compile_state(state: Type[BaseState]) -> dict:
|
||||
try:
|
||||
initial_state = state(_reflex_internal_init=True).dict(initial=True)
|
||||
except Exception as e:
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d__%H-%M-%S")
|
||||
constants.Reflex.LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
log_path = constants.Reflex.LOGS_DIR / f"state_compile_error_{timestamp}.log"
|
||||
traceback.TracebackException.from_exception(e).print(file=log_path.open("w+"))
|
||||
console.warn(
|
||||
f"Failed to compile initial state with computed vars, excluding them: {e}"
|
||||
f"Failed to compile initial state with computed vars. Error log saved to {log_path}"
|
||||
)
|
||||
initial_state = state(_reflex_internal_init=True).dict(
|
||||
initial=True, include_computed=False
|
||||
)
|
||||
return initial_state
|
||||
try:
|
||||
_ = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
pass
|
||||
else:
|
||||
if is_in_app_harness():
|
||||
# Playwright tests already have an event loop running, so we can't use asyncio.run.
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
resolved_initial_state = pool.submit(
|
||||
asyncio.run, _resolve_delta(initial_state)
|
||||
).result()
|
||||
console.warn(
|
||||
f"Had to get initial state in a thread 🤮 {resolved_initial_state}",
|
||||
)
|
||||
return resolved_initial_state
|
||||
|
||||
# Normally the compile runs before any event loop starts, we asyncio.run is available for calling.
|
||||
return asyncio.run(_resolve_delta(initial_state))
|
||||
|
||||
|
||||
def _compile_client_storage_field(
|
||||
@ -291,8 +318,9 @@ def compile_custom_component(
|
||||
"name": component.tag,
|
||||
"props": props,
|
||||
"render": render.render(),
|
||||
"hooks": {**render._get_all_hooks_internal(), **render._get_all_hooks()},
|
||||
"hooks": render._get_all_hooks(),
|
||||
"custom_code": render._get_all_custom_code(),
|
||||
"dynamic_imports": render._get_all_dynamic_imports(),
|
||||
},
|
||||
imports,
|
||||
)
|
||||
@ -495,7 +523,7 @@ def empty_dir(path: str | Path, keep_files: list[str] | None = None):
|
||||
path_ops.rm(element)
|
||||
|
||||
|
||||
def is_valid_url(url) -> bool:
|
||||
def is_valid_url(url: str) -> bool:
|
||||
"""Check if a url is valid.
|
||||
|
||||
Args:
|
||||
|
@ -9,6 +9,7 @@ from reflex.components.tags import Tag
|
||||
from reflex.components.tags.tagless import Tagless
|
||||
from reflex.utils.imports import ParsedImportDict
|
||||
from reflex.vars import BooleanVar, ObjectVar, Var
|
||||
from reflex.vars.base import VarData
|
||||
|
||||
|
||||
class Bare(Component):
|
||||
@ -30,9 +31,9 @@ class Bare(Component):
|
||||
return cls(contents=contents)
|
||||
else:
|
||||
contents = str(contents) if contents is not None else ""
|
||||
return cls(contents=contents) # type: ignore
|
||||
return cls(contents=contents)
|
||||
|
||||
def _get_all_hooks_internal(self) -> dict[str, None]:
|
||||
def _get_all_hooks_internal(self) -> dict[str, VarData | None]:
|
||||
"""Include the hooks for the component.
|
||||
|
||||
Returns:
|
||||
@ -43,7 +44,7 @@ class Bare(Component):
|
||||
hooks |= self.contents._var_value._get_all_hooks_internal()
|
||||
return hooks
|
||||
|
||||
def _get_all_hooks(self) -> dict[str, None]:
|
||||
def _get_all_hooks(self) -> dict[str, VarData | None]:
|
||||
"""Include the hooks for the component.
|
||||
|
||||
Returns:
|
||||
@ -107,11 +108,14 @@ class Bare(Component):
|
||||
return Tagless(contents=f"{{{self.contents!s}}}")
|
||||
return Tagless(contents=str(self.contents))
|
||||
|
||||
def _get_vars(self, include_children: bool = False) -> Iterator[Var]:
|
||||
def _get_vars(
|
||||
self, include_children: bool = False, ignore_ids: set[int] | None = None
|
||||
) -> Iterator[Var]:
|
||||
"""Walk all Vars used in this component.
|
||||
|
||||
Args:
|
||||
include_children: Whether to include Vars from children.
|
||||
ignore_ids: The ids to ignore.
|
||||
|
||||
Yields:
|
||||
The contents if it is a Var, otherwise nothing.
|
||||
|
@ -11,10 +11,11 @@ from reflex.event import EventHandler, set_clipboard
|
||||
from reflex.state import FrontendEventExceptionState
|
||||
from reflex.vars.base import Var
|
||||
from reflex.vars.function import ArgsFunctionOperation
|
||||
from reflex.vars.object import ObjectVar
|
||||
|
||||
|
||||
def on_error_spec(
|
||||
error: Var[Dict[str, str]], info: Var[Dict[str, str]]
|
||||
error: ObjectVar[Dict[str, str]], info: ObjectVar[Dict[str, str]]
|
||||
) -> Tuple[Var[str], Var[str]]:
|
||||
"""The spec for the on_error event handler.
|
||||
|
||||
|
@ -9,9 +9,10 @@ from reflex.components.component import Component
|
||||
from reflex.event import BASE_STATE, EventType
|
||||
from reflex.style import Style
|
||||
from reflex.vars.base import Var
|
||||
from reflex.vars.object import ObjectVar
|
||||
|
||||
def on_error_spec(
|
||||
error: Var[Dict[str, str]], info: Var[Dict[str, str]]
|
||||
error: ObjectVar[Dict[str, str]], info: ObjectVar[Dict[str, str]]
|
||||
) -> Tuple[Var[str], Var[str]]: ...
|
||||
|
||||
class ErrorBoundary(Component):
|
||||
|
@ -53,11 +53,11 @@ class Description(Meta):
|
||||
"""A component that displays the title of the current page."""
|
||||
|
||||
# The type of the description.
|
||||
name: str = "description"
|
||||
name: str | None = "description"
|
||||
|
||||
|
||||
class Image(Meta):
|
||||
"""A component that displays the title of the current page."""
|
||||
|
||||
# The type of the image.
|
||||
property: str = "og:image"
|
||||
property: str | None = "og:image"
|
||||
|
10
reflex/components/base/strict_mode.py
Normal file
10
reflex/components/base/strict_mode.py
Normal file
@ -0,0 +1,10 @@
|
||||
"""Module for the StrictMode component."""
|
||||
|
||||
from reflex.components.component import Component
|
||||
|
||||
|
||||
class StrictMode(Component):
|
||||
"""A React strict mode component to enable strict mode for its children."""
|
||||
|
||||
library = "react"
|
||||
tag = "StrictMode"
|
57
reflex/components/base/strict_mode.pyi
Normal file
57
reflex/components/base/strict_mode.pyi
Normal file
@ -0,0 +1,57 @@
|
||||
"""Stub file for reflex/components/base/strict_mode.py"""
|
||||
|
||||
# ------------------- DO NOT EDIT ----------------------
|
||||
# This file was generated by `reflex/utils/pyi_generator.py`!
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Dict, Optional, Union, overload
|
||||
|
||||
from reflex.components.component import Component
|
||||
from reflex.event import BASE_STATE, EventType
|
||||
from reflex.style import Style
|
||||
from reflex.vars.base import Var
|
||||
|
||||
class StrictMode(Component):
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # type: ignore
|
||||
cls,
|
||||
*children,
|
||||
style: Optional[Style] = None,
|
||||
key: Optional[Any] = None,
|
||||
id: Optional[Any] = None,
|
||||
class_name: Optional[Any] = None,
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
|
||||
on_blur: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_click: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_context_menu: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_double_click: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_focus: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mount: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_down: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_move: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_out: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_over: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_up: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_scroll: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_unmount: Optional[EventType[[], BASE_STATE]] = None,
|
||||
**props,
|
||||
) -> "StrictMode":
|
||||
"""Create the component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
style: The style of the component.
|
||||
key: A unique key for the component.
|
||||
id: The id for the component.
|
||||
class_name: The class name for the component.
|
||||
autofocus: Whether the component should take the focus once the page is loaded
|
||||
custom_attrs: custom attribute
|
||||
**props: The props of the component.
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
"""
|
||||
...
|
@ -43,13 +43,8 @@ from reflex.constants.state import FRONTEND_EVENT_STATE
|
||||
from reflex.event import (
|
||||
EventCallback,
|
||||
EventChain,
|
||||
EventChainVar,
|
||||
EventHandler,
|
||||
EventSpec,
|
||||
EventVar,
|
||||
call_event_fn,
|
||||
call_event_handler,
|
||||
get_handler_args,
|
||||
no_args_event_spec,
|
||||
)
|
||||
from reflex.style import Style, format_as_emotion
|
||||
@ -104,7 +99,7 @@ class BaseComponent(Base, ABC):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def _get_all_hooks_internal(self) -> dict[str, None]:
|
||||
def _get_all_hooks_internal(self) -> dict[str, VarData | None]:
|
||||
"""Get the reflex internal hooks for the component and its children.
|
||||
|
||||
Returns:
|
||||
@ -112,7 +107,7 @@ class BaseComponent(Base, ABC):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def _get_all_hooks(self) -> dict[str, None]:
|
||||
def _get_all_hooks(self) -> dict[str, VarData | None]:
|
||||
"""Get the React hooks for this component.
|
||||
|
||||
Returns:
|
||||
@ -155,13 +150,13 @@ class BaseComponent(Base, ABC):
|
||||
class ComponentNamespace(SimpleNamespace):
|
||||
"""A namespace to manage components with subcomponents."""
|
||||
|
||||
def __hash__(self) -> int:
|
||||
def __hash__(self) -> int: # pyright: ignore [reportIncompatibleVariableOverride]
|
||||
"""Get the hash of the namespace.
|
||||
|
||||
Returns:
|
||||
The hash of the namespace.
|
||||
"""
|
||||
return hash(self.__class__.__name__)
|
||||
return hash(type(self).__name__)
|
||||
|
||||
|
||||
def evaluate_style_namespaces(style: ComponentStyle) -> dict:
|
||||
@ -431,20 +426,22 @@ class Component(BaseComponent, ABC):
|
||||
else:
|
||||
continue
|
||||
|
||||
def determine_key(value: Any):
|
||||
# Try to create a var from the value
|
||||
key = value if isinstance(value, Var) else LiteralVar.create(value)
|
||||
|
||||
# Check that the var type is not None.
|
||||
if key is None:
|
||||
raise TypeError
|
||||
|
||||
return key
|
||||
|
||||
# Check whether the key is a component prop.
|
||||
if types._issubclass(field_type, Var):
|
||||
# Used to store the passed types if var type is a union.
|
||||
passed_types = None
|
||||
try:
|
||||
# Try to create a var from the value.
|
||||
if isinstance(value, Var):
|
||||
kwargs[key] = value
|
||||
else:
|
||||
kwargs[key] = LiteralVar.create(value)
|
||||
|
||||
# Check that the var type is not None.
|
||||
if kwargs[key] is None:
|
||||
raise TypeError
|
||||
kwargs[key] = determine_key(value)
|
||||
|
||||
expected_type = fields[key].outer_type_.__args__[0]
|
||||
# validate literal fields.
|
||||
@ -465,9 +462,7 @@ class Component(BaseComponent, ABC):
|
||||
if types.is_union(passed_type):
|
||||
# We need to check all possible types in the union.
|
||||
passed_types = (
|
||||
arg
|
||||
for arg in passed_type.__args__ # type: ignore
|
||||
if arg is not type(None)
|
||||
arg for arg in passed_type.__args__ if arg is not type(None)
|
||||
)
|
||||
if (
|
||||
# If the passed var is a union, check if all possible types are valid.
|
||||
@ -493,9 +488,8 @@ class Component(BaseComponent, ABC):
|
||||
)
|
||||
# Check if the key is an event trigger.
|
||||
if key in component_specific_triggers:
|
||||
# Temporarily disable full control for event triggers.
|
||||
kwargs["event_triggers"][key] = self._create_event_chain(
|
||||
value=value, # type: ignore
|
||||
kwargs["event_triggers"][key] = EventChain.create(
|
||||
value=value,
|
||||
args_spec=component_specific_triggers[key],
|
||||
key=key,
|
||||
)
|
||||
@ -548,104 +542,6 @@ class Component(BaseComponent, ABC):
|
||||
# Construct the component.
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _create_event_chain(
|
||||
self,
|
||||
args_spec: types.ArgsSpec | Sequence[types.ArgsSpec],
|
||||
value: Union[
|
||||
Var,
|
||||
EventHandler,
|
||||
EventSpec,
|
||||
List[Union[EventHandler, EventSpec, EventVar]],
|
||||
Callable,
|
||||
],
|
||||
key: Optional[str] = None,
|
||||
) -> Union[EventChain, Var]:
|
||||
"""Create an event chain from a variety of input types.
|
||||
|
||||
Args:
|
||||
args_spec: The args_spec of the event trigger being bound.
|
||||
value: The value to create the event chain from.
|
||||
key: The key of the event trigger being bound.
|
||||
|
||||
Returns:
|
||||
The event chain.
|
||||
|
||||
Raises:
|
||||
ValueError: If the value is not a valid event chain.
|
||||
"""
|
||||
# If it's an event chain var, return it.
|
||||
if isinstance(value, Var):
|
||||
if isinstance(value, EventChainVar):
|
||||
return value
|
||||
elif isinstance(value, EventVar):
|
||||
value = [value]
|
||||
elif issubclass(value._var_type, (EventChain, EventSpec)):
|
||||
return self._create_event_chain(args_spec, value.guess_type(), key=key)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid event chain: {value!s} of type {value._var_type}"
|
||||
)
|
||||
elif isinstance(value, EventChain):
|
||||
# Trust that the caller knows what they're doing passing an EventChain directly
|
||||
return value
|
||||
|
||||
# If the input is a single event handler, wrap it in a list.
|
||||
if isinstance(value, (EventHandler, EventSpec)):
|
||||
value = [value]
|
||||
|
||||
# If the input is a list of event handlers, create an event chain.
|
||||
if isinstance(value, List):
|
||||
events: List[Union[EventSpec, EventVar]] = []
|
||||
for v in value:
|
||||
if isinstance(v, (EventHandler, EventSpec)):
|
||||
# Call the event handler to get the event.
|
||||
events.append(call_event_handler(v, args_spec, key=key))
|
||||
elif isinstance(v, Callable):
|
||||
# Call the lambda to get the event chain.
|
||||
result = call_event_fn(v, args_spec, key=key)
|
||||
if isinstance(result, Var):
|
||||
raise ValueError(
|
||||
f"Invalid event chain: {v}. Cannot use a Var-returning "
|
||||
"lambda inside an EventChain list."
|
||||
)
|
||||
events.extend(result)
|
||||
elif isinstance(v, EventVar):
|
||||
events.append(v)
|
||||
else:
|
||||
raise ValueError(f"Invalid event: {v}")
|
||||
|
||||
# If the input is a callable, create an event chain.
|
||||
elif isinstance(value, Callable):
|
||||
result = call_event_fn(value, args_spec, key=key)
|
||||
if isinstance(result, Var):
|
||||
# Recursively call this function if the lambda returned an EventChain Var.
|
||||
return self._create_event_chain(args_spec, result, key=key)
|
||||
events = [*result]
|
||||
|
||||
# Otherwise, raise an error.
|
||||
else:
|
||||
raise ValueError(f"Invalid event chain: {value}")
|
||||
|
||||
# Add args to the event specs if necessary.
|
||||
events = [
|
||||
(e.with_args(get_handler_args(e)) if isinstance(e, EventSpec) else e)
|
||||
for e in events
|
||||
]
|
||||
|
||||
# Return the event chain.
|
||||
if isinstance(args_spec, Var):
|
||||
return EventChain(
|
||||
events=events,
|
||||
args_spec=None,
|
||||
event_actions={},
|
||||
)
|
||||
else:
|
||||
return EventChain(
|
||||
events=events,
|
||||
args_spec=args_spec,
|
||||
event_actions={},
|
||||
)
|
||||
|
||||
def get_event_triggers(
|
||||
self,
|
||||
) -> Dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]]:
|
||||
@ -653,7 +549,6 @@ class Component(BaseComponent, ABC):
|
||||
|
||||
Returns:
|
||||
The event triggers.
|
||||
|
||||
"""
|
||||
default_triggers: Dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]] = {
|
||||
EventTriggers.ON_FOCUS: no_args_event_spec,
|
||||
@ -681,7 +576,7 @@ class Component(BaseComponent, ABC):
|
||||
annotation = field.annotation
|
||||
if (metadata := getattr(annotation, "__metadata__", None)) is not None:
|
||||
args_spec = metadata[0]
|
||||
default_triggers[field.name] = args_spec or (no_args_event_spec) # type: ignore
|
||||
default_triggers[field.name] = args_spec or (no_args_event_spec)
|
||||
return default_triggers
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@ -728,8 +623,7 @@ class Component(BaseComponent, ABC):
|
||||
if props is None:
|
||||
# Add component props to the tag.
|
||||
props = {
|
||||
attr[:-1] if attr.endswith("_") else attr: getattr(self, attr)
|
||||
for attr in self.get_props()
|
||||
attr.removesuffix("_"): getattr(self, attr) for attr in self.get_props()
|
||||
}
|
||||
|
||||
# Add ref to element if `id` is not None.
|
||||
@ -807,22 +701,21 @@ class Component(BaseComponent, ABC):
|
||||
# Import here to avoid circular imports.
|
||||
from reflex.components.base.bare import Bare
|
||||
from reflex.components.base.fragment import Fragment
|
||||
from reflex.utils.exceptions import ComponentTypeError
|
||||
from reflex.utils.exceptions import ChildrenTypeError
|
||||
|
||||
# Filter out None props
|
||||
props = {key: value for key, value in props.items() if value is not None}
|
||||
|
||||
def validate_children(children):
|
||||
def validate_children(children: tuple | list):
|
||||
for child in children:
|
||||
if isinstance(child, tuple):
|
||||
if isinstance(child, (tuple, list)):
|
||||
validate_children(child)
|
||||
|
||||
# Make sure the child is a valid type.
|
||||
if not types._isinstance(child, ComponentChild):
|
||||
raise ComponentTypeError(
|
||||
"Children of Reflex components must be other components, "
|
||||
"state vars, or primitive Python types. "
|
||||
f"Got child {child} of type {type(child)}.",
|
||||
)
|
||||
if isinstance(child, dict) or not types._isinstance(
|
||||
child, ComponentChild
|
||||
):
|
||||
raise ChildrenTypeError(component=cls.__name__, child=child)
|
||||
|
||||
# Validate all the children.
|
||||
validate_children(children)
|
||||
@ -865,7 +758,7 @@ class Component(BaseComponent, ABC):
|
||||
|
||||
# Walk the MRO to call all `add_style` methods.
|
||||
for base in self._iter_parent_classes_with_method("add_style"):
|
||||
s = base.add_style(self) # type: ignore
|
||||
s = base.add_style(self)
|
||||
if s is not None:
|
||||
styles.append(s)
|
||||
|
||||
@ -957,7 +850,7 @@ class Component(BaseComponent, ABC):
|
||||
else {}
|
||||
)
|
||||
|
||||
def render(self) -> Dict:
|
||||
def render(self) -> dict:
|
||||
"""Render the component.
|
||||
|
||||
Returns:
|
||||
@ -975,7 +868,7 @@ class Component(BaseComponent, ABC):
|
||||
self._replace_prop_names(rendered_dict)
|
||||
return rendered_dict
|
||||
|
||||
def _replace_prop_names(self, rendered_dict) -> None:
|
||||
def _replace_prop_names(self, rendered_dict: dict) -> None:
|
||||
"""Replace the prop names in the render dictionary.
|
||||
|
||||
Args:
|
||||
@ -1015,7 +908,7 @@ class Component(BaseComponent, ABC):
|
||||
comp.__name__ for comp in (Fragment, Foreach, Cond, Match)
|
||||
]
|
||||
|
||||
def validate_child(child):
|
||||
def validate_child(child: Any):
|
||||
child_name = type(child).__name__
|
||||
|
||||
# Iterate through the immediate children of fragment
|
||||
@ -1087,18 +980,22 @@ class Component(BaseComponent, ABC):
|
||||
event_args.append(spec)
|
||||
yield event_trigger, event_args
|
||||
|
||||
def _get_vars(self, include_children: bool = False) -> list[Var]:
|
||||
def _get_vars(
|
||||
self, include_children: bool = False, ignore_ids: set[int] | None = None
|
||||
) -> Iterator[Var]:
|
||||
"""Walk all Vars used in this component.
|
||||
|
||||
Args:
|
||||
include_children: Whether to include Vars from children.
|
||||
ignore_ids: The ids to ignore.
|
||||
|
||||
Returns:
|
||||
Yields:
|
||||
Each var referenced by the component (props, styles, event handlers).
|
||||
"""
|
||||
vars = getattr(self, "__vars", None)
|
||||
ignore_ids = ignore_ids or set()
|
||||
vars: List[Var] | None = getattr(self, "__vars", None)
|
||||
if vars is not None:
|
||||
return vars
|
||||
yield from vars
|
||||
vars = self.__vars = []
|
||||
# Get Vars associated with event trigger arguments.
|
||||
for _, event_vars in self._get_vars_from_event_triggers(self.event_triggers):
|
||||
@ -1142,12 +1039,15 @@ class Component(BaseComponent, ABC):
|
||||
# Get Vars associated with children.
|
||||
if include_children:
|
||||
for child in self.children:
|
||||
if not isinstance(child, Component):
|
||||
if not isinstance(child, Component) or id(child) in ignore_ids:
|
||||
continue
|
||||
child_vars = child._get_vars(include_children=include_children)
|
||||
ignore_ids.add(id(child))
|
||||
child_vars = child._get_vars(
|
||||
include_children=include_children, ignore_ids=ignore_ids
|
||||
)
|
||||
vars.extend(child_vars)
|
||||
|
||||
return vars
|
||||
yield from vars
|
||||
|
||||
def _event_trigger_values_use_state(self) -> bool:
|
||||
"""Check if the values of a component's event trigger use state.
|
||||
@ -1209,7 +1109,7 @@ class Component(BaseComponent, ABC):
|
||||
Yields:
|
||||
The parent classes that define the method (differently than the base).
|
||||
"""
|
||||
seen_methods = set([getattr(Component, method)])
|
||||
seen_methods = {getattr(Component, method)}
|
||||
for clz in cls.mro():
|
||||
if clz is Component:
|
||||
break
|
||||
@ -1339,7 +1239,7 @@ class Component(BaseComponent, ABC):
|
||||
"""
|
||||
_imports = {}
|
||||
|
||||
if self._get_ref_hook():
|
||||
if self._get_ref_hook() is not None:
|
||||
# Handle hooks needed for attaching react refs to DOM nodes.
|
||||
_imports.setdefault("react", set()).add(ImportVar(tag="useRef"))
|
||||
_imports.setdefault(f"$/{Dirs.STATE_PATH}", set()).add(
|
||||
@ -1369,7 +1269,9 @@ class Component(BaseComponent, ABC):
|
||||
if user_hooks_data is not None:
|
||||
other_imports.append(user_hooks_data.imports)
|
||||
other_imports.extend(
|
||||
hook_imports for hook_imports in self._get_added_hooks().values()
|
||||
hook_vardata.imports
|
||||
for hook_vardata in self._get_added_hooks().values()
|
||||
if hook_vardata is not None
|
||||
)
|
||||
|
||||
return imports.merge_imports(_imports, *other_imports)
|
||||
@ -1391,15 +1293,9 @@ class Component(BaseComponent, ABC):
|
||||
|
||||
# Collect imports from Vars used directly by this component.
|
||||
var_datas = [var._get_all_var_data() for var in self._get_vars()]
|
||||
var_imports: List[ImmutableParsedImportDict] = list(
|
||||
map(
|
||||
lambda var_data: var_data.imports,
|
||||
filter(
|
||||
None,
|
||||
var_datas,
|
||||
),
|
||||
)
|
||||
)
|
||||
var_imports: List[ImmutableParsedImportDict] = [
|
||||
var_data.imports for var_data in var_datas if var_data is not None
|
||||
]
|
||||
|
||||
added_import_dicts: list[ParsedImportDict] = []
|
||||
for clz in self._iter_parent_classes_with_method("add_imports"):
|
||||
@ -1408,8 +1304,9 @@ class Component(BaseComponent, ABC):
|
||||
if not isinstance(list_of_import_dict, list):
|
||||
list_of_import_dict = [list_of_import_dict]
|
||||
|
||||
for import_dict in list_of_import_dict:
|
||||
added_import_dicts.append(parse_imports(import_dict))
|
||||
added_import_dicts.extend(
|
||||
[parse_imports(import_dict) for import_dict in list_of_import_dict]
|
||||
)
|
||||
|
||||
return imports.merge_imports(
|
||||
*self._get_props_imports(),
|
||||
@ -1458,7 +1355,7 @@ class Component(BaseComponent, ABC):
|
||||
}}
|
||||
}}, []);"""
|
||||
|
||||
def _get_ref_hook(self) -> str | None:
|
||||
def _get_ref_hook(self) -> Var | None:
|
||||
"""Generate the ref hook for the component.
|
||||
|
||||
Returns:
|
||||
@ -1466,11 +1363,12 @@ class Component(BaseComponent, ABC):
|
||||
"""
|
||||
ref = self.get_ref()
|
||||
if ref is not None:
|
||||
return (
|
||||
f"const {ref} = useRef(null); {Var(_js_expr=ref)._as_ref()!s} = {ref};"
|
||||
return Var(
|
||||
f"const {ref} = useRef(null); {Var(_js_expr=ref)._as_ref()!s} = {ref};",
|
||||
_var_data=VarData(position=Hooks.HookPosition.INTERNAL),
|
||||
)
|
||||
|
||||
def _get_vars_hooks(self) -> dict[str, None]:
|
||||
def _get_vars_hooks(self) -> dict[str, VarData | None]:
|
||||
"""Get the hooks required by vars referenced in this component.
|
||||
|
||||
Returns:
|
||||
@ -1483,27 +1381,38 @@ class Component(BaseComponent, ABC):
|
||||
vars_hooks.update(
|
||||
var_data.hooks
|
||||
if isinstance(var_data.hooks, dict)
|
||||
else {k: None for k in var_data.hooks}
|
||||
else {
|
||||
k: VarData(position=Hooks.HookPosition.INTERNAL)
|
||||
for k in var_data.hooks
|
||||
}
|
||||
)
|
||||
return vars_hooks
|
||||
|
||||
def _get_events_hooks(self) -> dict[str, None]:
|
||||
def _get_events_hooks(self) -> dict[str, VarData | None]:
|
||||
"""Get the hooks required by events referenced in this component.
|
||||
|
||||
Returns:
|
||||
The hooks for the events.
|
||||
"""
|
||||
return {Hooks.EVENTS: None} if self.event_triggers else {}
|
||||
return (
|
||||
{Hooks.EVENTS: VarData(position=Hooks.HookPosition.INTERNAL)}
|
||||
if self.event_triggers
|
||||
else {}
|
||||
)
|
||||
|
||||
def _get_special_hooks(self) -> dict[str, None]:
|
||||
def _get_special_hooks(self) -> dict[str, VarData | None]:
|
||||
"""Get the hooks required by special actions referenced in this component.
|
||||
|
||||
Returns:
|
||||
The hooks for special actions.
|
||||
"""
|
||||
return {Hooks.AUTOFOCUS: None} if self.autofocus else {}
|
||||
return (
|
||||
{Hooks.AUTOFOCUS: VarData(position=Hooks.HookPosition.INTERNAL)}
|
||||
if self.autofocus
|
||||
else {}
|
||||
)
|
||||
|
||||
def _get_hooks_internal(self) -> dict[str, None]:
|
||||
def _get_hooks_internal(self) -> dict[str, VarData | None]:
|
||||
"""Get the React hooks for this component managed by the framework.
|
||||
|
||||
Downstream components should NOT override this method to avoid breaking
|
||||
@ -1514,7 +1423,7 @@ class Component(BaseComponent, ABC):
|
||||
"""
|
||||
return {
|
||||
**{
|
||||
hook: None
|
||||
str(hook): VarData(position=Hooks.HookPosition.INTERNAL)
|
||||
for hook in [self._get_ref_hook(), self._get_mount_lifecycle_hook()]
|
||||
if hook is not None
|
||||
},
|
||||
@ -1523,7 +1432,7 @@ class Component(BaseComponent, ABC):
|
||||
**self._get_special_hooks(),
|
||||
}
|
||||
|
||||
def _get_added_hooks(self) -> dict[str, ImportDict]:
|
||||
def _get_added_hooks(self) -> dict[str, VarData | None]:
|
||||
"""Get the hooks added via `add_hooks` method.
|
||||
|
||||
Returns:
|
||||
@ -1532,17 +1441,15 @@ class Component(BaseComponent, ABC):
|
||||
code = {}
|
||||
|
||||
def extract_var_hooks(hook: Var):
|
||||
_imports = {}
|
||||
var_data = VarData.merge(hook._get_all_var_data())
|
||||
if var_data is not None:
|
||||
for sub_hook in var_data.hooks:
|
||||
code[sub_hook] = {}
|
||||
if var_data.imports:
|
||||
_imports = var_data.imports
|
||||
code[sub_hook] = None
|
||||
|
||||
if str(hook) in code:
|
||||
code[str(hook)] = imports.merge_imports(code[str(hook)], _imports)
|
||||
code[str(hook)] = VarData.merge(var_data, code[str(hook)])
|
||||
else:
|
||||
code[str(hook)] = _imports
|
||||
code[str(hook)] = var_data
|
||||
|
||||
# Add the hook code from add_hooks for each parent class (this is reversed to preserve
|
||||
# the order of the hooks in the final output)
|
||||
@ -1551,7 +1458,7 @@ class Component(BaseComponent, ABC):
|
||||
if isinstance(hook, Var):
|
||||
extract_var_hooks(hook)
|
||||
else:
|
||||
code[hook] = {}
|
||||
code[hook] = None
|
||||
|
||||
return code
|
||||
|
||||
@ -1565,7 +1472,7 @@ class Component(BaseComponent, ABC):
|
||||
"""
|
||||
return
|
||||
|
||||
def _get_all_hooks_internal(self) -> dict[str, None]:
|
||||
def _get_all_hooks_internal(self) -> dict[str, VarData | None]:
|
||||
"""Get the reflex internal hooks for the component and its children.
|
||||
|
||||
Returns:
|
||||
@ -1580,7 +1487,7 @@ class Component(BaseComponent, ABC):
|
||||
|
||||
return code
|
||||
|
||||
def _get_all_hooks(self) -> dict[str, None]:
|
||||
def _get_all_hooks(self) -> dict[str, VarData | None]:
|
||||
"""Get the React hooks for this component and its children.
|
||||
|
||||
Returns:
|
||||
@ -1588,13 +1495,15 @@ class Component(BaseComponent, ABC):
|
||||
"""
|
||||
code = {}
|
||||
|
||||
# Add the internal hooks for this component.
|
||||
code.update(self._get_hooks_internal())
|
||||
|
||||
# Add the hook code for this component.
|
||||
hooks = self._get_hooks()
|
||||
if hooks is not None:
|
||||
code[hooks] = None
|
||||
|
||||
for hook in self._get_added_hooks():
|
||||
code[hook] = None
|
||||
code.update(self._get_added_hooks())
|
||||
|
||||
# Add the hook code for the children.
|
||||
for child in self.children:
|
||||
@ -1744,7 +1653,7 @@ class CustomComponent(Component):
|
||||
|
||||
# Handle event chains.
|
||||
if types._issubclass(type_, EventChain):
|
||||
value = self._create_event_chain(
|
||||
value = EventChain.create(
|
||||
value=value,
|
||||
args_spec=event_triggers_in_component_declaration.get(
|
||||
key, no_args_event_spec
|
||||
@ -1762,7 +1671,7 @@ class CustomComponent(Component):
|
||||
if base_value is not None and isinstance(value, Component):
|
||||
self.component_props[key] = value
|
||||
value = base_value._replace(
|
||||
merge_var_data=VarData( # type: ignore
|
||||
merge_var_data=VarData(
|
||||
imports=value._get_all_imports(),
|
||||
hooks=value._get_all_hooks(),
|
||||
)
|
||||
@ -1795,7 +1704,7 @@ class CustomComponent(Component):
|
||||
return hash(self.tag)
|
||||
|
||||
@classmethod
|
||||
def get_props(cls) -> Set[str]:
|
||||
def get_props(cls) -> Set[str]: # pyright: ignore [reportIncompatibleVariableOverride]
|
||||
"""Get the props for the component.
|
||||
|
||||
Returns:
|
||||
@ -1869,22 +1778,28 @@ class CustomComponent(Component):
|
||||
for name, prop in self.props.items()
|
||||
]
|
||||
|
||||
def _get_vars(self, include_children: bool = False) -> list[Var]:
|
||||
def _get_vars(
|
||||
self, include_children: bool = False, ignore_ids: set[int] | None = None
|
||||
) -> Iterator[Var]:
|
||||
"""Walk all Vars used in this component.
|
||||
|
||||
Args:
|
||||
include_children: Whether to include Vars from children.
|
||||
ignore_ids: The ids to ignore.
|
||||
|
||||
Returns:
|
||||
Yields:
|
||||
Each var referenced by the component (props, styles, event handlers).
|
||||
"""
|
||||
return (
|
||||
super()._get_vars(include_children=include_children)
|
||||
+ [prop for prop in self.props.values() if isinstance(prop, Var)]
|
||||
+ self.get_component(self)._get_vars(include_children=include_children)
|
||||
ignore_ids = ignore_ids or set()
|
||||
yield from super()._get_vars(
|
||||
include_children=include_children, ignore_ids=ignore_ids
|
||||
)
|
||||
yield from filter(lambda prop: isinstance(prop, Var), self.props.values())
|
||||
yield from self.get_component(self)._get_vars(
|
||||
include_children=include_children, ignore_ids=ignore_ids
|
||||
)
|
||||
|
||||
@lru_cache(maxsize=None) # noqa
|
||||
@lru_cache(maxsize=None) # noqa: B019
|
||||
def get_component(self) -> Component:
|
||||
"""Render the component.
|
||||
|
||||
@ -2028,7 +1943,7 @@ class StatefulComponent(BaseComponent):
|
||||
|
||||
if not should_memoize:
|
||||
# Determine if any Vars have associated data.
|
||||
for prop_var in component._get_vars():
|
||||
for prop_var in component._get_vars(include_children=True):
|
||||
if prop_var._get_all_var_data():
|
||||
should_memoize = True
|
||||
break
|
||||
@ -2196,6 +2111,31 @@ class StatefulComponent(BaseComponent):
|
||||
]
|
||||
return [var_name]
|
||||
|
||||
@staticmethod
|
||||
def _get_deps_from_event_trigger(event: EventChain | EventSpec | Var) -> set[str]:
|
||||
"""Get the dependencies accessed by event triggers.
|
||||
|
||||
Args:
|
||||
event: The event trigger to extract deps from.
|
||||
|
||||
Returns:
|
||||
The dependencies accessed by the event triggers.
|
||||
"""
|
||||
events: list = [event]
|
||||
deps = set()
|
||||
|
||||
if isinstance(event, EventChain):
|
||||
events.extend(event.events)
|
||||
|
||||
for ev in events:
|
||||
if isinstance(ev, EventSpec):
|
||||
for arg in ev.args:
|
||||
for a in arg:
|
||||
var_datas = VarData.merge(a._get_all_var_data())
|
||||
if var_datas and var_datas.deps is not None:
|
||||
deps |= {str(dep) for dep in var_datas.deps}
|
||||
return deps
|
||||
|
||||
@classmethod
|
||||
def _get_memoized_event_triggers(
|
||||
cls,
|
||||
@ -2232,6 +2172,11 @@ class StatefulComponent(BaseComponent):
|
||||
|
||||
# Calculate Var dependencies accessed by the handler for useCallback dep array.
|
||||
var_deps = ["addEvents", "Event"]
|
||||
|
||||
# Get deps from event trigger var data.
|
||||
var_deps.extend(cls._get_deps_from_event_trigger(event))
|
||||
|
||||
# Get deps from hooks.
|
||||
for arg in event_args:
|
||||
var_data = arg._get_all_var_data()
|
||||
if var_data is None:
|
||||
@ -2254,7 +2199,7 @@ class StatefulComponent(BaseComponent):
|
||||
)
|
||||
return trigger_memo
|
||||
|
||||
def _get_all_hooks_internal(self) -> dict[str, None]:
|
||||
def _get_all_hooks_internal(self) -> dict[str, VarData | None]:
|
||||
"""Get the reflex internal hooks for the component and its children.
|
||||
|
||||
Returns:
|
||||
@ -2262,7 +2207,7 @@ class StatefulComponent(BaseComponent):
|
||||
"""
|
||||
return {}
|
||||
|
||||
def _get_all_hooks(self) -> dict[str, None]:
|
||||
def _get_all_hooks(self) -> dict[str, VarData | None]:
|
||||
"""Get the React hooks for this component.
|
||||
|
||||
Returns:
|
||||
@ -2380,9 +2325,9 @@ class MemoizationLeaf(Component):
|
||||
The memoization leaf
|
||||
"""
|
||||
comp = super().create(*children, **props)
|
||||
if comp._get_all_hooks() or comp._get_all_hooks_internal():
|
||||
comp._memoization_mode = cls._memoization_mode.copy(
|
||||
update={"disposition": MemoizationDisposition.ALWAYS}
|
||||
if comp._get_all_hooks():
|
||||
comp._memoization_mode = dataclasses.replace(
|
||||
comp._memoization_mode, disposition=MemoizationDisposition.ALWAYS
|
||||
)
|
||||
return comp
|
||||
|
||||
@ -2443,7 +2388,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
|
||||
if tag["name"] == "match":
|
||||
element = tag["cond"]
|
||||
|
||||
conditionals = tag["default"]
|
||||
conditionals = render_dict_to_var(tag["default"], imported_names)
|
||||
|
||||
for case in tag["match_cases"][::-1]:
|
||||
condition = case[0].to_string() == element.to_string()
|
||||
@ -2452,7 +2397,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
|
||||
|
||||
conditionals = ternary_operation(
|
||||
condition,
|
||||
case[-1],
|
||||
render_dict_to_var(case[-1], imported_names),
|
||||
conditionals,
|
||||
)
|
||||
|
||||
@ -2511,6 +2456,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
slots=True,
|
||||
)
|
||||
class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
|
||||
"""A Var that represents a Component."""
|
||||
@ -2565,7 +2511,7 @@ class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
|
||||
Returns:
|
||||
The hash of the var.
|
||||
"""
|
||||
return hash((self.__class__.__name__, self._js_expr))
|
||||
return hash((type(self).__name__, self._js_expr))
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
|
@ -4,8 +4,10 @@ from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from reflex import constants
|
||||
from reflex.components.component import Component
|
||||
from reflex.components.core.cond import cond
|
||||
from reflex.components.datadisplay.logo import svg_logo
|
||||
from reflex.components.el.elements.typography import Div
|
||||
from reflex.components.lucide.icon import Icon
|
||||
from reflex.components.radix.themes.components.dialog import (
|
||||
@ -25,7 +27,7 @@ from reflex.vars.function import FunctionStringVar
|
||||
from reflex.vars.number import BooleanVar
|
||||
from reflex.vars.sequence import LiteralArrayVar
|
||||
|
||||
connect_error_var_data: VarData = VarData( # type: ignore
|
||||
connect_error_var_data: VarData = VarData(
|
||||
imports=Imports.EVENTS,
|
||||
hooks={Hooks.EVENTS: None},
|
||||
)
|
||||
@ -99,14 +101,14 @@ class ConnectionToaster(Toaster):
|
||||
"""
|
||||
toast_id = "websocket-error"
|
||||
target_url = WebsocketTargetURL.create()
|
||||
props = ToastProps( # type: ignore
|
||||
props = ToastProps(
|
||||
description=LiteralVar.create(
|
||||
f"Check if server is reachable at {target_url}",
|
||||
),
|
||||
close_button=True,
|
||||
duration=120000,
|
||||
id=toast_id,
|
||||
)
|
||||
) # pyright: ignore [reportCallIssue]
|
||||
|
||||
individual_hooks = [
|
||||
f"const toast_props = {LiteralVar.create(props)!s};",
|
||||
@ -116,7 +118,7 @@ class ConnectionToaster(Toaster):
|
||||
_var_data=VarData(
|
||||
imports={
|
||||
"react": ["useEffect", "useState"],
|
||||
**dict(target_url._get_all_var_data().imports), # type: ignore
|
||||
**dict(target_url._get_all_var_data().imports), # pyright: ignore [reportArgumentType, reportOptionalMemberAccess]
|
||||
}
|
||||
),
|
||||
).call(
|
||||
@ -241,7 +243,7 @@ class WifiOffPulse(Icon):
|
||||
size=props.pop("size", 32),
|
||||
z_index=props.pop("z_index", 9999),
|
||||
position=props.pop("position", "fixed"),
|
||||
bottom=props.pop("botton", "33px"),
|
||||
bottom=props.pop("bottom", "33px"),
|
||||
right=props.pop("right", "33px"),
|
||||
animation=LiteralVar.create(f"{pulse_var} 1s infinite"),
|
||||
**props,
|
||||
@ -293,7 +295,84 @@ class ConnectionPulser(Div):
|
||||
)
|
||||
|
||||
|
||||
class BackendDisabled(Div):
|
||||
"""A component that displays a message when the backend is disabled."""
|
||||
|
||||
@classmethod
|
||||
def create(cls, **props) -> Component:
|
||||
"""Create a backend disabled component.
|
||||
|
||||
Args:
|
||||
**props: The properties of the component.
|
||||
|
||||
Returns:
|
||||
The backend disabled component.
|
||||
"""
|
||||
import reflex as rx
|
||||
|
||||
is_backend_disabled = Var(
|
||||
"backendDisabled",
|
||||
_var_type=bool,
|
||||
_var_data=VarData(
|
||||
hooks={
|
||||
"const [backendDisabled, setBackendDisabled] = useState(false);": None,
|
||||
"useEffect(() => { setBackendDisabled(isBackendDisabled()); }, []);": None,
|
||||
},
|
||||
imports={
|
||||
f"$/{constants.Dirs.STATE_PATH}": [
|
||||
ImportVar(tag="isBackendDisabled")
|
||||
],
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
return super().create(
|
||||
rx.cond(
|
||||
is_backend_disabled,
|
||||
rx.box(
|
||||
rx.box(
|
||||
rx.card(
|
||||
rx.vstack(
|
||||
svg_logo(),
|
||||
rx.text(
|
||||
"You ran out of compute credits.",
|
||||
),
|
||||
rx.callout(
|
||||
rx.fragment(
|
||||
"Please upgrade your plan or raise your compute credits at ",
|
||||
rx.link(
|
||||
"Reflex Cloud.",
|
||||
href="https://cloud.reflex.dev/",
|
||||
),
|
||||
),
|
||||
width="100%",
|
||||
icon="info",
|
||||
variant="surface",
|
||||
),
|
||||
),
|
||||
font_size="20px",
|
||||
font_family='"Inter", "Helvetica", "Arial", sans-serif',
|
||||
variant="classic",
|
||||
),
|
||||
position="fixed",
|
||||
top="50%",
|
||||
left="50%",
|
||||
transform="translate(-50%, -50%)",
|
||||
width="40ch",
|
||||
max_width="90vw",
|
||||
),
|
||||
position="fixed",
|
||||
z_index=9999,
|
||||
backdrop_filter="grayscale(1) blur(5px)",
|
||||
width="100dvw",
|
||||
height="100dvh",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
connection_banner = ConnectionBanner.create
|
||||
connection_modal = ConnectionModal.create
|
||||
connection_toaster = ConnectionToaster.create
|
||||
connection_pulser = ConnectionPulser.create
|
||||
backend_disabled = BackendDisabled.create
|
||||
|
@ -321,7 +321,7 @@ class ConnectionPulser(Div):
|
||||
"""Create a connection pulser component.
|
||||
|
||||
Args:
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -350,7 +350,93 @@ class ConnectionPulser(Div):
|
||||
"""
|
||||
...
|
||||
|
||||
class BackendDisabled(Div):
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # type: ignore
|
||||
cls,
|
||||
*children,
|
||||
access_key: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
auto_capitalize: Optional[
|
||||
Union[Var[Union[bool, int, str]], bool, int, str]
|
||||
] = None,
|
||||
content_editable: Optional[
|
||||
Union[Var[Union[bool, int, str]], bool, int, str]
|
||||
] = None,
|
||||
context_menu: Optional[
|
||||
Union[Var[Union[bool, int, str]], bool, int, str]
|
||||
] = None,
|
||||
dir: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
draggable: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
enter_key_hint: Optional[
|
||||
Union[Var[Union[bool, int, str]], bool, int, str]
|
||||
] = None,
|
||||
hidden: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
input_mode: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
item_prop: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
role: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
slot: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
spell_check: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
tab_index: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
title: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
style: Optional[Style] = None,
|
||||
key: Optional[Any] = None,
|
||||
id: Optional[Any] = None,
|
||||
class_name: Optional[Any] = None,
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
|
||||
on_blur: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_click: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_context_menu: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_double_click: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_focus: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mount: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_down: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_move: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_out: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_over: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_mouse_up: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_scroll: Optional[EventType[[], BASE_STATE]] = None,
|
||||
on_unmount: Optional[EventType[[], BASE_STATE]] = None,
|
||||
**props,
|
||||
) -> "BackendDisabled":
|
||||
"""Create a backend disabled component.
|
||||
|
||||
Args:
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
dir: Defines the text direction. Allowed values are ltr (Left-To-Right) or rtl (Right-To-Left)
|
||||
draggable: Defines whether the element can be dragged.
|
||||
enter_key_hint: Hints what media types the media element is able to play.
|
||||
hidden: Defines whether the element is hidden.
|
||||
input_mode: Defines the type of the element.
|
||||
item_prop: Defines the name of the element for metadata purposes.
|
||||
lang: Defines the language used in the element.
|
||||
role: Defines the role of the element.
|
||||
slot: Assigns a slot in a shadow DOM shadow tree to an element.
|
||||
spell_check: Defines whether the element may be checked for spelling errors.
|
||||
tab_index: Defines the position of the current element in the tabbing order.
|
||||
title: Defines a tooltip for the element.
|
||||
style: The style of the component.
|
||||
key: A unique key for the component.
|
||||
id: The id for the component.
|
||||
class_name: The class name for the component.
|
||||
autofocus: Whether the component should take the focus once the page is loaded
|
||||
custom_attrs: custom attribute
|
||||
**props: The properties of the component.
|
||||
|
||||
Returns:
|
||||
The backend disabled component.
|
||||
"""
|
||||
...
|
||||
|
||||
connection_banner = ConnectionBanner.create
|
||||
connection_modal = ConnectionModal.create
|
||||
connection_toaster = ConnectionToaster.create
|
||||
connection_pulser = ConnectionPulser.create
|
||||
backend_disabled = BackendDisabled.create
|
||||
|
@ -58,7 +58,7 @@ class Breakpoints(Dict[K, V]):
|
||||
|
||||
Args:
|
||||
custom: Custom mapping using CSS values or variables.
|
||||
initial: Styling when in the inital width
|
||||
initial: Styling when in the initial width
|
||||
xs: Styling when in the extra-small width
|
||||
sm: Styling when in the small width
|
||||
md: Styling when in the medium width
|
||||
@ -82,7 +82,9 @@ class Breakpoints(Dict[K, V]):
|
||||
return Breakpoints(
|
||||
{
|
||||
k: v
|
||||
for k, v in zip(["initial", *breakpoint_names], thresholds)
|
||||
for k, v in zip(
|
||||
["initial", *breakpoint_names], thresholds, strict=True
|
||||
)
|
||||
if v is not None
|
||||
}
|
||||
)
|
||||
|
@ -24,7 +24,7 @@ class ClientSideRouting(Component):
|
||||
library = "$/utils/client_side_routing"
|
||||
tag = "useClientSideRouting"
|
||||
|
||||
def add_hooks(self) -> list[str]:
|
||||
def add_hooks(self) -> list[str | Var]:
|
||||
"""Get the hooks to render.
|
||||
|
||||
Returns:
|
||||
@ -41,7 +41,7 @@ class ClientSideRouting(Component):
|
||||
return ""
|
||||
|
||||
|
||||
def wait_for_client_redirect(component) -> Component:
|
||||
def wait_for_client_redirect(component: Component) -> Component:
|
||||
"""Wait for a redirect to occur before rendering a component.
|
||||
|
||||
This prevents the 404 page from flashing while the redirect is happening.
|
||||
@ -66,4 +66,4 @@ class Default404Page(Component):
|
||||
tag = "Error"
|
||||
is_default = True
|
||||
|
||||
status_code: Var[int] = 404 # type: ignore
|
||||
status_code: Var[int] = Var.create(404)
|
||||
|
@ -13,7 +13,7 @@ from reflex.vars.base import Var
|
||||
route_not_found: Var
|
||||
|
||||
class ClientSideRouting(Component):
|
||||
def add_hooks(self) -> list[str]: ...
|
||||
def add_hooks(self) -> list[str | Var]: ...
|
||||
def render(self) -> str: ...
|
||||
@overload
|
||||
@classmethod
|
||||
@ -60,7 +60,7 @@ class ClientSideRouting(Component):
|
||||
"""
|
||||
...
|
||||
|
||||
def wait_for_client_redirect(component) -> Component: ...
|
||||
def wait_for_client_redirect(component: Component) -> Component: ...
|
||||
|
||||
class Default404Page(Component):
|
||||
@overload
|
||||
|
@ -6,11 +6,12 @@ from typing import Dict, List, Tuple, Union
|
||||
|
||||
from reflex.components.base.fragment import Fragment
|
||||
from reflex.components.tags.tag import Tag
|
||||
from reflex.constants.compiler import Hooks
|
||||
from reflex.event import EventChain, EventHandler, passthrough_event_spec
|
||||
from reflex.utils.format import format_prop, wrap
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.vars import get_unique_variable_name
|
||||
from reflex.vars.base import Var
|
||||
from reflex.vars.base import Var, VarData
|
||||
|
||||
|
||||
class Clipboard(Fragment):
|
||||
@ -72,7 +73,7 @@ class Clipboard(Fragment):
|
||||
),
|
||||
}
|
||||
|
||||
def add_hooks(self) -> list[str]:
|
||||
def add_hooks(self) -> list[str | Var[str]]:
|
||||
"""Add hook to register paste event listener.
|
||||
|
||||
Returns:
|
||||
@ -83,13 +84,14 @@ class Clipboard(Fragment):
|
||||
return []
|
||||
if isinstance(on_paste, EventChain):
|
||||
on_paste = wrap(str(format_prop(on_paste)).strip("{}"), "(")
|
||||
hook_expr = f"usePasteHandler({self.targets!s}, {self.on_paste_event_actions!s}, {on_paste!s})"
|
||||
|
||||
return [
|
||||
"usePasteHandler(%s, %s, %s)"
|
||||
% (
|
||||
str(self.targets),
|
||||
str(self.on_paste_event_actions),
|
||||
on_paste,
|
||||
)
|
||||
Var(
|
||||
hook_expr,
|
||||
_var_type="str",
|
||||
_var_data=VarData(position=Hooks.HookPosition.POST_TRIGGER),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
@ -71,6 +71,6 @@ class Clipboard(Fragment):
|
||||
...
|
||||
|
||||
def add_imports(self) -> dict[str, ImportVar]: ...
|
||||
def add_hooks(self) -> list[str]: ...
|
||||
def add_hooks(self) -> list[str | Var[str]]: ...
|
||||
|
||||
clipboard = Clipboard.create
|
||||
|
@ -26,10 +26,9 @@ class Cond(MemoizationLeaf):
|
||||
cond: Var[Any]
|
||||
|
||||
# The component to render if the cond is true.
|
||||
comp1: BaseComponent = None # type: ignore
|
||||
|
||||
comp1: BaseComponent | None = None
|
||||
# The component to render if the cond is false.
|
||||
comp2: BaseComponent = None # type: ignore
|
||||
comp2: BaseComponent | None = None
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
@ -49,9 +48,9 @@ class Cond(MemoizationLeaf):
|
||||
The conditional component.
|
||||
"""
|
||||
# Wrap everything in fragments.
|
||||
if comp1.__class__.__name__ != "Fragment":
|
||||
if type(comp1).__name__ != "Fragment":
|
||||
comp1 = Fragment.create(comp1)
|
||||
if comp2 is None or comp2.__class__.__name__ != "Fragment":
|
||||
if comp2 is None or type(comp2).__name__ != "Fragment":
|
||||
comp2 = Fragment.create(comp2) if comp2 else Fragment.create()
|
||||
return Fragment.create(
|
||||
cls(
|
||||
@ -73,8 +72,8 @@ class Cond(MemoizationLeaf):
|
||||
def _render(self) -> Tag:
|
||||
return CondTag(
|
||||
cond=self.cond,
|
||||
true_value=self.comp1.render(),
|
||||
false_value=self.comp2.render(),
|
||||
true_value=self.comp1.render(), # pyright: ignore [reportOptionalMemberAccess]
|
||||
false_value=self.comp2.render(), # pyright: ignore [reportOptionalMemberAccess]
|
||||
)
|
||||
|
||||
def render(self) -> Dict:
|
||||
@ -111,7 +110,7 @@ class Cond(MemoizationLeaf):
|
||||
|
||||
|
||||
@overload
|
||||
def cond(condition: Any, c1: Component, c2: Any) -> Component: ...
|
||||
def cond(condition: Any, c1: Component, c2: Any) -> Component: ... # pyright: ignore [reportOverlappingOverload]
|
||||
|
||||
|
||||
@overload
|
||||
@ -154,7 +153,7 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
|
||||
if c2 is None:
|
||||
raise ValueError("For conditional vars, the second argument must be set.")
|
||||
|
||||
def create_var(cond_part):
|
||||
def create_var(cond_part: Any) -> Var[Any]:
|
||||
return LiteralVar.create(cond_part)
|
||||
|
||||
# convert the truth and false cond parts into vars so the _var_data can be obtained.
|
||||
@ -163,16 +162,16 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
|
||||
|
||||
# Create the conditional var.
|
||||
return ternary_operation(
|
||||
cond_var.bool()._replace( # type: ignore
|
||||
cond_var.bool()._replace(
|
||||
merge_var_data=VarData(imports=_IS_TRUE_IMPORT),
|
||||
), # type: ignore
|
||||
),
|
||||
c1,
|
||||
c2,
|
||||
)
|
||||
|
||||
|
||||
@overload
|
||||
def color_mode_cond(light: Component, dark: Component | None = None) -> Component: ... # type: ignore
|
||||
def color_mode_cond(light: Component, dark: Component | None = None) -> Component: ... # pyright: ignore [reportOverlappingOverload]
|
||||
|
||||
|
||||
@overload
|
||||
|
@ -28,7 +28,7 @@ class DebounceInput(Component):
|
||||
min_length: Var[int]
|
||||
|
||||
# Time to wait between end of input and triggering on_change
|
||||
debounce_timeout: Var[int] = DEFAULT_DEBOUNCE_TIMEOUT # type: ignore
|
||||
debounce_timeout: Var[int] = Var.create(DEFAULT_DEBOUNCE_TIMEOUT)
|
||||
|
||||
# If true, notify when Enter key is pressed
|
||||
force_notify_by_enter: Var[bool]
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
from typing import Any, Callable, Iterable
|
||||
|
||||
@ -10,6 +11,7 @@ from reflex.components.component import Component
|
||||
from reflex.components.tags import IterTag
|
||||
from reflex.constants import MemoizationMode
|
||||
from reflex.state import ComponentState
|
||||
from reflex.utils.exceptions import UntypedVarError
|
||||
from reflex.vars.base import LiteralVar, Var
|
||||
|
||||
|
||||
@ -50,6 +52,7 @@ class Foreach(Component):
|
||||
Raises:
|
||||
ForeachVarError: If the iterable is of type Any.
|
||||
TypeError: If the render function is a ComponentState.
|
||||
UntypedVarError: If the iterable is of type Any without a type annotation.
|
||||
"""
|
||||
iterable = LiteralVar.create(iterable)
|
||||
if iterable._var_type == Any:
|
||||
@ -71,8 +74,14 @@ class Foreach(Component):
|
||||
iterable=iterable,
|
||||
render_fn=render_fn,
|
||||
)
|
||||
# Keep a ref to a rendered component to determine correct imports/hooks/styles.
|
||||
component.children = [component._render().render_component()]
|
||||
try:
|
||||
# Keep a ref to a rendered component to determine correct imports/hooks/styles.
|
||||
component.children = [component._render().render_component()]
|
||||
except UntypedVarError as e:
|
||||
raise UntypedVarError(
|
||||
f"Could not foreach over var `{iterable!s}` without a type annotation. "
|
||||
"See https://reflex.dev/docs/library/dynamic-rendering/foreach/"
|
||||
) from e
|
||||
return component
|
||||
|
||||
def _render(self) -> IterTag:
|
||||
@ -97,9 +106,20 @@ class Foreach(Component):
|
||||
# Determine the index var name based on the params accepted by render_fn.
|
||||
props["index_var_name"] = params[1].name
|
||||
else:
|
||||
render_fn = self.render_fn
|
||||
# Otherwise, use a deterministic index, based on the render function bytecode.
|
||||
code_hash = (
|
||||
hash(self.render_fn.__code__)
|
||||
hash(
|
||||
getattr(
|
||||
render_fn,
|
||||
"__code__",
|
||||
(
|
||||
repr(self.render_fn)
|
||||
if not isinstance(render_fn, functools.partial)
|
||||
else render_fn.func.__code__
|
||||
),
|
||||
)
|
||||
)
|
||||
.to_bytes(
|
||||
length=8,
|
||||
byteorder="big",
|
||||
|
@ -14,7 +14,7 @@ class Html(Div):
|
||||
"""
|
||||
|
||||
# The HTML to render.
|
||||
dangerouslySetInnerHTML: Var[Dict[str, str]]
|
||||
dangerouslySetInnerHTML: Var[Dict[str, str]] # noqa: N815
|
||||
|
||||
@classmethod
|
||||
def create(cls, *children, **props):
|
||||
|
@ -71,7 +71,7 @@ class Html(Div):
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
dangerouslySetInnerHTML: The HTML to render.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
|
@ -109,7 +109,7 @@ class Match(MemoizationLeaf):
|
||||
return cases, default
|
||||
|
||||
@classmethod
|
||||
def _create_case_var_with_var_data(cls, case_element):
|
||||
def _create_case_var_with_var_data(cls, case_element: Any) -> Var:
|
||||
"""Convert a case element into a Var.If the case
|
||||
is a Style type, we extract the var data and merge it with the
|
||||
newly created Var.
|
||||
@ -222,7 +222,7 @@ class Match(MemoizationLeaf):
|
||||
cond=match_cond_var,
|
||||
match_cases=match_cases,
|
||||
default=default,
|
||||
children=[case[-1] for case in match_cases] + [default], # type: ignore
|
||||
children=[case[-1] for case in match_cases] + [default], # pyright: ignore [reportArgumentType]
|
||||
)
|
||||
)
|
||||
|
||||
@ -236,13 +236,13 @@ class Match(MemoizationLeaf):
|
||||
_js_expr=format.format_match(
|
||||
cond=str(match_cond_var),
|
||||
match_cases=match_cases,
|
||||
default=default, # type: ignore
|
||||
default=default, # pyright: ignore [reportArgumentType]
|
||||
),
|
||||
_var_type=default._var_type, # type: ignore
|
||||
_var_type=default._var_type, # pyright: ignore [reportAttributeAccessIssue,reportOptionalMemberAccess]
|
||||
_var_data=VarData.merge(
|
||||
match_cond_var._get_all_var_data(),
|
||||
*[el._get_all_var_data() for case in match_cases for el in case],
|
||||
default._get_all_var_data(), # type: ignore
|
||||
default._get_all_var_data(), # pyright: ignore [reportAttributeAccessIssue, reportOptionalMemberAccess]
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -29,7 +29,7 @@ from reflex.event import (
|
||||
from reflex.utils import format
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.vars import VarData
|
||||
from reflex.vars.base import CallableVar, LiteralVar, Var, get_unique_variable_name
|
||||
from reflex.vars.base import CallableVar, Var, get_unique_variable_name
|
||||
from reflex.vars.sequence import LiteralStringVar
|
||||
|
||||
DEFAULT_UPLOAD_ID: str = "default"
|
||||
@ -108,7 +108,8 @@ def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec:
|
||||
# UploadFilesProvider assigns a special function to clear selected files
|
||||
# into the shared global refs object to make it accessible outside a React
|
||||
# component via `run_script` (otherwise backend could never clear files).
|
||||
return run_script(f"refs['__clear_selected_files']({id_!r})")
|
||||
func = Var("__clear_selected_files")._as_ref()
|
||||
return run_script(f"{func}({id_!r})")
|
||||
|
||||
|
||||
def cancel_upload(upload_id: str) -> EventSpec:
|
||||
@ -120,7 +121,8 @@ def cancel_upload(upload_id: str) -> EventSpec:
|
||||
Returns:
|
||||
An event spec that cancels the upload when triggered.
|
||||
"""
|
||||
return run_script(f"upload_controllers[{LiteralVar.create(upload_id)!s}]?.abort()")
|
||||
controller = Var(f"__upload_controllers_{upload_id}")._as_ref()
|
||||
return run_script(f"{controller}?.abort()")
|
||||
|
||||
|
||||
def get_upload_dir() -> Path:
|
||||
@ -190,7 +192,7 @@ class GhostUpload(Fragment):
|
||||
class Upload(MemoizationLeaf):
|
||||
"""A file upload component."""
|
||||
|
||||
library = "react-dropzone@14.2.10"
|
||||
library = "react-dropzone@14.3.5"
|
||||
|
||||
tag = ""
|
||||
|
||||
@ -267,7 +269,7 @@ class Upload(MemoizationLeaf):
|
||||
on_drop = upload_props["on_drop"]
|
||||
if isinstance(on_drop, Callable):
|
||||
# Call the lambda to get the event chain.
|
||||
on_drop = call_event_fn(on_drop, _on_drop_spec) # type: ignore
|
||||
on_drop = call_event_fn(on_drop, _on_drop_spec)
|
||||
if isinstance(on_drop, EventSpec):
|
||||
# Update the provided args for direct use with on_drop.
|
||||
on_drop = on_drop.with_args(
|
||||
|
@ -14,7 +14,7 @@ from reflex.components.radix.themes.layout.box import Box
|
||||
from reflex.constants.colors import Color
|
||||
from reflex.event import set_clipboard
|
||||
from reflex.style import Style
|
||||
from reflex.utils import console, format
|
||||
from reflex.utils import format
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.vars.base import LiteralVar, Var, VarData
|
||||
|
||||
@ -382,7 +382,7 @@ for theme_name in dir(Theme):
|
||||
class CodeBlock(Component, MarkdownComponentMap):
|
||||
"""A code block."""
|
||||
|
||||
library = "react-syntax-highlighter@15.6.0"
|
||||
library = "react-syntax-highlighter@15.6.1"
|
||||
|
||||
tag = "PrismAsyncLight"
|
||||
|
||||
@ -438,6 +438,8 @@ class CodeBlock(Component, MarkdownComponentMap):
|
||||
can_copy = props.pop("can_copy", False)
|
||||
copy_button = props.pop("copy_button", None)
|
||||
|
||||
# react-syntax-highlighter doesn't have an explicit "light" or "dark" theme so we use one-light and one-dark
|
||||
# themes respectively to ensure code compatibility.
|
||||
if "theme" not in props:
|
||||
# Default color scheme responds to global color mode.
|
||||
props["theme"] = color_mode_cond(
|
||||
@ -445,20 +447,9 @@ class CodeBlock(Component, MarkdownComponentMap):
|
||||
dark=Theme.one_dark,
|
||||
)
|
||||
|
||||
# react-syntax-highlighter doesnt have an explicit "light" or "dark" theme so we use one-light and one-dark
|
||||
# themes respectively to ensure code compatibility.
|
||||
if "theme" in props and not isinstance(props["theme"], Var):
|
||||
props["theme"] = getattr(Theme, format.to_snake_case(props["theme"])) # type: ignore
|
||||
console.deprecate(
|
||||
feature_name="theme prop as string",
|
||||
reason="Use code_block.themes instead.",
|
||||
deprecation_version="0.6.0",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
|
||||
if can_copy:
|
||||
code = children[0]
|
||||
copy_button = ( # type: ignore
|
||||
copy_button = (
|
||||
copy_button
|
||||
if copy_button is not None
|
||||
else Button.create(
|
||||
@ -502,8 +493,8 @@ class CodeBlock(Component, MarkdownComponentMap):
|
||||
|
||||
theme = self.theme
|
||||
|
||||
out.add_props(style=theme).remove_props("theme", "code", "language").add_props(
|
||||
children=self.code, language=_LANGUAGE
|
||||
out.add_props(style=theme).remove_props("theme", "code").add_props(
|
||||
children=self.code,
|
||||
)
|
||||
|
||||
return out
|
||||
@ -512,20 +503,25 @@ class CodeBlock(Component, MarkdownComponentMap):
|
||||
return ["can_copy", "copy_button"]
|
||||
|
||||
@classmethod
|
||||
def _get_language_registration_hook(cls) -> str:
|
||||
def _get_language_registration_hook(cls, language_var: Var = _LANGUAGE) -> str:
|
||||
"""Get the hook to register the language.
|
||||
|
||||
Args:
|
||||
language_var: The const/literal Var of the language module to import.
|
||||
For markdown, uses the default placeholder _LANGUAGE. For direct use,
|
||||
a LiteralStringVar should be passed via the language prop.
|
||||
|
||||
Returns:
|
||||
The hook to register the language.
|
||||
"""
|
||||
return f"""
|
||||
if ({_LANGUAGE!s}) {{
|
||||
if ({language_var!s}) {{
|
||||
(async () => {{
|
||||
try {{
|
||||
const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{_LANGUAGE!s}}}`);
|
||||
SyntaxHighlighter.registerLanguage({_LANGUAGE!s}, module.default);
|
||||
const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{language_var!s}}}`);
|
||||
SyntaxHighlighter.registerLanguage({language_var!s}, module.default);
|
||||
}} catch (error) {{
|
||||
console.error(`Error importing language module for ${{{_LANGUAGE!s}}}:`, error);
|
||||
console.error(`Error importing language module for ${{{language_var!s}}}:`, error);
|
||||
}}
|
||||
}})();
|
||||
}}
|
||||
@ -547,8 +543,7 @@ class CodeBlock(Component, MarkdownComponentMap):
|
||||
The hooks for the component.
|
||||
"""
|
||||
return [
|
||||
f"const {_LANGUAGE!s} = {self.language!s}",
|
||||
self._get_language_registration_hook(),
|
||||
self._get_language_registration_hook(language_var=self.language),
|
||||
]
|
||||
|
||||
|
||||
|
@ -51,27 +51,6 @@ class GridColumnIcons(Enum):
|
||||
VideoUri = "video_uri"
|
||||
|
||||
|
||||
# @serializer
|
||||
# def serialize_gridcolumn_icon(icon: GridColumnIcons) -> str:
|
||||
# """Serialize grid column icon.
|
||||
|
||||
# Args:
|
||||
# icon: the Icon to serialize.
|
||||
|
||||
# Returns:
|
||||
# The serialized value.
|
||||
# """
|
||||
# return "prefix" + str(icon)
|
||||
|
||||
|
||||
# class DataEditorColumn(Base):
|
||||
# """Column."""
|
||||
|
||||
# title: str
|
||||
# id: Optional[str] = None
|
||||
# type_: str = "str"
|
||||
|
||||
|
||||
class DataEditorTheme(Base):
|
||||
"""The theme for the DataEditor component."""
|
||||
|
||||
@ -186,7 +165,7 @@ class DataEditor(NoSSRComponent):
|
||||
|
||||
tag = "DataEditor"
|
||||
is_default = True
|
||||
library: str = "@glideapps/glide-data-grid@^6.0.3"
|
||||
library: str | None = "@glideapps/glide-data-grid@^6.0.3"
|
||||
lib_dependencies: List[str] = [
|
||||
"lodash@^4.17.21",
|
||||
"react-responsive-carousel@^3.2.7",
|
||||
@ -229,7 +208,7 @@ class DataEditor(NoSSRComponent):
|
||||
header_height: Var[int]
|
||||
|
||||
# Additional header icons:
|
||||
# header_icons: Var[Any] # (TODO: must be a map of name: svg)
|
||||
# header_icons: Var[Any] # (TODO: must be a map of name: svg) #noqa: ERA001
|
||||
|
||||
# The maximum width a column can be automatically sized to.
|
||||
max_column_auto_width: Var[int]
|
||||
@ -240,7 +219,7 @@ class DataEditor(NoSSRComponent):
|
||||
# The minimum width a column can be resized to.
|
||||
min_column_width: Var[int]
|
||||
|
||||
# Determins the height of each row.
|
||||
# Determines the height of each row.
|
||||
row_height: Var[int]
|
||||
|
||||
# Kind of row markers.
|
||||
@ -342,6 +321,8 @@ class DataEditor(NoSSRComponent):
|
||||
Returns:
|
||||
The import dict.
|
||||
"""
|
||||
if self.library is None:
|
||||
return {}
|
||||
return {
|
||||
"": f"{format.format_library_name(self.library)}/dist/index.css",
|
||||
self.library: "GridCellKind",
|
||||
@ -360,10 +341,13 @@ class DataEditor(NoSSRComponent):
|
||||
editor_id = get_unique_variable_name()
|
||||
|
||||
# Define the name of the getData callback associated with this component and assign to get_cell_content.
|
||||
data_callback = f"getData_{editor_id}"
|
||||
self.get_cell_content = Var(_js_expr=data_callback) # type: ignore
|
||||
if self.get_cell_content is not None:
|
||||
data_callback = self.get_cell_content._js_expr
|
||||
else:
|
||||
data_callback = f"getData_{editor_id}"
|
||||
self.get_cell_content = Var(_js_expr=data_callback)
|
||||
|
||||
code = [f"function {data_callback}([col, row])" "{"]
|
||||
code = [f"function {data_callback}([col, row]){{"]
|
||||
|
||||
columns_path = str(self.columns)
|
||||
data_path = str(self.data)
|
||||
@ -403,7 +387,8 @@ class DataEditor(NoSSRComponent):
|
||||
raise ValueError(
|
||||
"DataEditor data must be an ArrayVar if rows is not provided."
|
||||
)
|
||||
props["rows"] = data.length() if isinstance(data, Var) else len(data)
|
||||
|
||||
props["rows"] = data.length() if isinstance(data, ArrayVar) else len(data)
|
||||
|
||||
if not isinstance(columns, Var) and len(columns):
|
||||
if types.is_dataframe(type(data)) or (
|
||||
|
@ -288,10 +288,10 @@ class DataEditor(NoSSRComponent):
|
||||
freeze_columns: The number of columns which should remain in place when scrolling horizontally. Doesn't include rowMarkers.
|
||||
group_header_height: Controls the header of the group header row.
|
||||
header_height: Controls the height of the header row.
|
||||
max_column_auto_width: Additional header icons: header_icons: Var[Any] # (TODO: must be a map of name: svg) The maximum width a column can be automatically sized to.
|
||||
max_column_auto_width: The maximum width a column can be automatically sized to.
|
||||
max_column_width: The maximum width a column can be resized to.
|
||||
min_column_width: The minimum width a column can be resized to.
|
||||
row_height: Determins the height of each row.
|
||||
row_height: Determines the height of each row.
|
||||
row_markers: Kind of row markers.
|
||||
row_marker_start_index: Changes the starting index for row markers.
|
||||
row_marker_width: Sets the width of row markers in pixels, if unset row markers will automatically size.
|
||||
|
@ -15,10 +15,8 @@ def svg_logo(color: Union[str, rx.Var[str]] = rx.color_mode_cond("#110F1F", "whi
|
||||
The Reflex logo SVG.
|
||||
"""
|
||||
|
||||
def logo_path(d):
|
||||
return rx.el.svg.path(
|
||||
d=d,
|
||||
)
|
||||
def logo_path(d: str):
|
||||
return rx.el.svg.path(d=d)
|
||||
|
||||
paths = [
|
||||
"M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z",
|
||||
|
@ -490,17 +490,17 @@ class ShikiJsTransformer(ShikiBaseTransformers):
|
||||
},
|
||||
# White Space
|
||||
# ".tab, .space": {
|
||||
# "position": "relative",
|
||||
# "position": "relative", # noqa: ERA001
|
||||
# },
|
||||
# ".tab::before": {
|
||||
# "content": "'⇥'",
|
||||
# "position": "absolute",
|
||||
# "opacity": "0.3",
|
||||
# "content": "'⇥'", # noqa: ERA001
|
||||
# "position": "absolute", # noqa: ERA001
|
||||
# "opacity": "0.3",# noqa: ERA001
|
||||
# },
|
||||
# ".space::before": {
|
||||
# "content": "'·'",
|
||||
# "position": "absolute",
|
||||
# "opacity": "0.3",
|
||||
# "content": "'·'", # noqa: ERA001
|
||||
# "position": "absolute", # noqa: ERA001
|
||||
# "opacity": "0.3", # noqa: ERA001
|
||||
# },
|
||||
}
|
||||
)
|
||||
@ -602,7 +602,7 @@ class ShikiCodeBlock(Component, MarkdownComponentMap):
|
||||
|
||||
transformer_styles = {}
|
||||
# Collect styles from transformers and wrapper
|
||||
for transformer in code_block.transformers._var_value: # type: ignore
|
||||
for transformer in code_block.transformers._var_value: # pyright: ignore [reportAttributeAccessIssue]
|
||||
if isinstance(transformer, ShikiBaseTransformers) and transformer.style:
|
||||
transformer_styles.update(transformer.style)
|
||||
transformer_styles.update(code_wrapper_props.pop("style", {}))
|
||||
@ -621,18 +621,22 @@ class ShikiCodeBlock(Component, MarkdownComponentMap):
|
||||
|
||||
Returns:
|
||||
Imports for the component.
|
||||
|
||||
Raises:
|
||||
ValueError: If the transformers are not of type LiteralVar.
|
||||
"""
|
||||
imports = defaultdict(list)
|
||||
if not isinstance(self.transformers, LiteralVar):
|
||||
raise ValueError(
|
||||
f"transformers should be a LiteralVar type. Got {type(self.transformers)} instead."
|
||||
)
|
||||
for transformer in self.transformers._var_value:
|
||||
if isinstance(transformer, ShikiBaseTransformers):
|
||||
imports[transformer.library].extend(
|
||||
[ImportVar(tag=str(fn)) for fn in transformer.fns]
|
||||
)
|
||||
(
|
||||
if transformer.library not in self.lib_dependencies:
|
||||
self.lib_dependencies.append(transformer.library)
|
||||
if transformer.library not in self.lib_dependencies
|
||||
else None
|
||||
)
|
||||
return imports
|
||||
|
||||
@classmethod
|
||||
@ -653,8 +657,9 @@ class ShikiCodeBlock(Component, MarkdownComponentMap):
|
||||
raise ValueError(
|
||||
f"the function names should be str names of functions in the specified transformer: {library!r}"
|
||||
)
|
||||
return ShikiBaseTransformers( # type: ignore
|
||||
library=library, fns=[FunctionStringVar.create(fn) for fn in fns]
|
||||
return ShikiBaseTransformers(
|
||||
library=library,
|
||||
fns=[FunctionStringVar.create(fn) for fn in fns], # pyright: ignore [reportCallIssue]
|
||||
)
|
||||
|
||||
def _render(self, props: dict[str, Any] | None = None):
|
||||
@ -757,13 +762,13 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
|
||||
|
||||
if can_copy:
|
||||
code = children[0]
|
||||
copy_button = ( # type: ignore
|
||||
copy_button = (
|
||||
copy_button
|
||||
if copy_button is not None
|
||||
else Button.create(
|
||||
Icon.create(tag="copy", size=16, color=color("gray", 11)),
|
||||
on_click=[
|
||||
set_clipboard(cls._strip_transformer_triggers(code)), # type: ignore
|
||||
set_clipboard(cls._strip_transformer_triggers(code)),
|
||||
copy_script(),
|
||||
],
|
||||
style=Style(
|
||||
|
@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Union
|
||||
|
||||
from reflex import constants
|
||||
from reflex.utils import imports
|
||||
from reflex.utils.exceptions import DynamicComponentMissingLibrary
|
||||
from reflex.utils.exceptions import DynamicComponentMissingLibraryError
|
||||
from reflex.utils.format import format_library_name
|
||||
from reflex.utils.serializers import serializer
|
||||
from reflex.vars import Var, get_unique_variable_name
|
||||
@ -36,13 +36,15 @@ def bundle_library(component: Union["Component", str]):
|
||||
component: The component to bundle the library with.
|
||||
|
||||
Raises:
|
||||
DynamicComponentMissingLibrary: Raised when a dynamic component is missing a library.
|
||||
DynamicComponentMissingLibraryError: Raised when a dynamic component is missing a library.
|
||||
"""
|
||||
if isinstance(component, str):
|
||||
bundled_libraries.add(component)
|
||||
return
|
||||
if component.library is None:
|
||||
raise DynamicComponentMissingLibrary("Component must have a library to bundle.")
|
||||
raise DynamicComponentMissingLibraryError(
|
||||
"Component must have a library to bundle."
|
||||
)
|
||||
bundled_libraries.add(format_library_name(component.library))
|
||||
|
||||
|
||||
@ -136,6 +138,23 @@ def load_dynamic_serializer():
|
||||
|
||||
module_code_lines.insert(0, "const React = window.__reflex.react;")
|
||||
|
||||
function_line = next(
|
||||
index
|
||||
for index, line in enumerate(module_code_lines)
|
||||
if line.startswith("export default function")
|
||||
)
|
||||
|
||||
module_code_lines = [
|
||||
line
|
||||
for _, line in sorted(
|
||||
enumerate(module_code_lines),
|
||||
key=lambda x: (
|
||||
not (x[1].startswith("import ") and x[0] < function_line),
|
||||
x[0],
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
return "\n".join(
|
||||
[
|
||||
"//__reflex_evaluate",
|
||||
|
@ -48,4 +48,4 @@ PROP_TO_ELEMENTS = {
|
||||
ELEMENT_TO_PROPS = defaultdict(list)
|
||||
for prop, elements in PROP_TO_ELEMENTS.items():
|
||||
for el in elements:
|
||||
ELEMENT_TO_PROPS[el].append(prop) # type: ignore
|
||||
ELEMENT_TO_PROPS[el].append(prop)
|
||||
|
@ -6,7 +6,7 @@ from reflex.components.component import Component
|
||||
class Element(Component):
|
||||
"""The base class for all raw HTML elements."""
|
||||
|
||||
def __eq__(self, other):
|
||||
def __eq__(self, other: object):
|
||||
"""Two elements are equal if they have the same tag.
|
||||
|
||||
Args:
|
||||
|
@ -127,7 +127,7 @@ _MAPPING = {
|
||||
|
||||
|
||||
EXCLUDE = ["del_", "Del", "image"]
|
||||
for _, v in _MAPPING.items():
|
||||
for v in _MAPPING.values():
|
||||
v.extend([mod.capitalize() for mod in v if mod not in EXCLUDE])
|
||||
|
||||
_SUBMOD_ATTRS: dict[str, list[str]] = _MAPPING
|
||||
|
@ -339,5 +339,5 @@ _MAPPING = {
|
||||
],
|
||||
}
|
||||
EXCLUDE = ["del_", "Del", "image"]
|
||||
for _, v in _MAPPING.items():
|
||||
for v in _MAPPING.values():
|
||||
v.extend([mod.capitalize() for mod in v if mod not in EXCLUDE])
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Element classes. This is an auto-generated file. Do not edit. See ../generate.py."""
|
||||
"""Base classes."""
|
||||
|
||||
from typing import Union
|
||||
|
||||
@ -9,7 +9,7 @@ from reflex.vars.base import Var
|
||||
class BaseHTML(Element):
|
||||
"""Base class for common attributes."""
|
||||
|
||||
# Provides a hint for generating a keyboard shortcut for the current element.
|
||||
# Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Var[Union[str, int, bool]]
|
||||
|
||||
# Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
|
@ -67,7 +67,7 @@ class BaseHTML(Element):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Element classes. This is an auto-generated file. Do not edit. See ../generate.py."""
|
||||
"""Forms classes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -18,6 +18,7 @@ from reflex.event import (
|
||||
prevent_default,
|
||||
)
|
||||
from reflex.utils.imports import ImportDict
|
||||
from reflex.utils.types import is_optional
|
||||
from reflex.vars import VarData
|
||||
from reflex.vars.base import LiteralVar, Var
|
||||
|
||||
@ -84,7 +85,6 @@ class Datalist(BaseHTML):
|
||||
"""Display the datalist element."""
|
||||
|
||||
tag = "datalist"
|
||||
# No unique attributes, only common ones are inherited
|
||||
|
||||
|
||||
class Fieldset(Element):
|
||||
@ -102,7 +102,7 @@ class Fieldset(Element):
|
||||
name: Var[Union[str, int, bool]]
|
||||
|
||||
|
||||
def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
|
||||
def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]:
|
||||
"""Event handler spec for the on_submit event.
|
||||
|
||||
Returns:
|
||||
@ -111,7 +111,7 @@ def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
|
||||
return (FORM_DATA,)
|
||||
|
||||
|
||||
def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]:
|
||||
def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]:
|
||||
"""Event handler spec for the on_submit event.
|
||||
|
||||
Returns:
|
||||
@ -153,7 +153,7 @@ class Form(BaseHTML):
|
||||
target: Var[Union[str, int, bool]]
|
||||
|
||||
# If true, the form will be cleared after submit.
|
||||
reset_on_submit: Var[bool] = False # type: ignore
|
||||
reset_on_submit: Var[bool] = Var.create(False)
|
||||
|
||||
# The name used to make this form's submit handler function unique.
|
||||
handle_submit_unique_name: Var[str]
|
||||
@ -182,9 +182,7 @@ class Form(BaseHTML):
|
||||
props["handle_submit_unique_name"] = ""
|
||||
form = super().create(*children, **props)
|
||||
form.handle_submit_unique_name = md5(
|
||||
str({**form._get_all_hooks_internal(), **form._get_all_hooks()}).encode(
|
||||
"utf-8"
|
||||
)
|
||||
str(form._get_all_hooks()).encode("utf-8")
|
||||
).hexdigest()
|
||||
return form
|
||||
|
||||
@ -250,11 +248,14 @@ class Form(BaseHTML):
|
||||
_js_expr=f"getRefValue({ref_var!s})",
|
||||
_var_data=VarData.merge(ref_var._get_all_var_data()),
|
||||
)
|
||||
# print(repr(form_refs))
|
||||
return form_refs
|
||||
|
||||
def _get_vars(self, include_children: bool = True) -> Iterator[Var]:
|
||||
yield from super()._get_vars(include_children=include_children)
|
||||
def _get_vars(
|
||||
self, include_children: bool = True, ignore_ids: set[int] | None = None
|
||||
) -> Iterator[Var]:
|
||||
yield from super()._get_vars(
|
||||
include_children=include_children, ignore_ids=ignore_ids
|
||||
)
|
||||
yield from self._get_form_refs().values()
|
||||
|
||||
def _exclude_props(self) -> list[str]:
|
||||
@ -384,6 +385,33 @@ class Input(BaseHTML):
|
||||
# Fired when a key is released
|
||||
on_key_up: EventHandler[key_event]
|
||||
|
||||
@classmethod
|
||||
def create(cls, *children, **props):
|
||||
"""Create an Input component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
**props: The properties of the component.
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
"""
|
||||
from reflex.vars.number import ternary_operation
|
||||
|
||||
value = props.get("value")
|
||||
|
||||
# React expects an empty string(instead of null) for controlled inputs.
|
||||
if value is not None and is_optional(
|
||||
(value_var := Var.create(value))._var_type
|
||||
):
|
||||
props["value"] = ternary_operation(
|
||||
(value_var != Var.create(None)) # pyright: ignore [reportArgumentType]
|
||||
& (value_var != Var(_js_expr="undefined")),
|
||||
value,
|
||||
Var.create(""),
|
||||
)
|
||||
return super().create(*children, **props)
|
||||
|
||||
|
||||
class Label(BaseHTML):
|
||||
"""Display the label element."""
|
||||
@ -401,7 +429,6 @@ class Legend(BaseHTML):
|
||||
"""Display the legend element."""
|
||||
|
||||
tag = "legend"
|
||||
# No unique attributes, only common ones are inherited
|
||||
|
||||
|
||||
class Meter(BaseHTML):
|
||||
|
@ -103,7 +103,7 @@ class Button(BaseHTML):
|
||||
name: Name of the button, used when sending form data
|
||||
type: Type of the button (submit, reset, or button)
|
||||
value: Value of the button, used when sending form data
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -189,7 +189,7 @@ class Datalist(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: No unique attributes, only common ones are inherited Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -270,8 +270,8 @@ class Fieldset(Element):
|
||||
"""
|
||||
...
|
||||
|
||||
def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]: ...
|
||||
def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]: ...
|
||||
def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]: ...
|
||||
def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]: ...
|
||||
|
||||
class Form(BaseHTML):
|
||||
@overload
|
||||
@ -341,10 +341,10 @@ class Form(BaseHTML):
|
||||
on_submit: Optional[
|
||||
Union[
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||
],
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||
],
|
||||
]
|
||||
] = None,
|
||||
@ -367,7 +367,7 @@ class Form(BaseHTML):
|
||||
reset_on_submit: If true, the form will be cleared after submit.
|
||||
handle_submit_unique_name: The name used to make this form's submit handler function unique.
|
||||
on_submit: Fired when the form is submitted
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -512,7 +512,7 @@ class Input(BaseHTML):
|
||||
on_unmount: Optional[EventType[[], BASE_STATE]] = None,
|
||||
**props,
|
||||
) -> "Input":
|
||||
"""Create the component.
|
||||
"""Create an Input component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
@ -554,7 +554,7 @@ class Input(BaseHTML):
|
||||
on_blur: Fired when the input loses focus
|
||||
on_key_down: Fired when a key is pressed down
|
||||
on_key_up: Fired when a key is released
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -576,7 +576,7 @@ class Input(BaseHTML):
|
||||
class_name: The class name for the component.
|
||||
autofocus: Whether the component should take the focus once the page is loaded
|
||||
custom_attrs: custom attribute
|
||||
**props: The props of the component.
|
||||
**props: The properties of the component.
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
@ -644,7 +644,7 @@ class Label(BaseHTML):
|
||||
*children: The children of the component.
|
||||
html_for: ID of a form control with which the label is associated
|
||||
form: Associates the label with a form (by id)
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -730,7 +730,7 @@ class Legend(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: No unique attributes, only common ones are inherited Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -830,7 +830,7 @@ class Meter(BaseHTML):
|
||||
min: Minimum value of the range
|
||||
optimum: Optimum value in the range
|
||||
value: Current value of the meter
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -920,7 +920,7 @@ class Optgroup(BaseHTML):
|
||||
*children: The children of the component.
|
||||
disabled: Disables the optgroup
|
||||
label: Label for the optgroup
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1014,7 +1014,7 @@ class Option(BaseHTML):
|
||||
label: Label for the option, if the text is not the label
|
||||
selected: Indicates that the option is initially selected
|
||||
value: Value to be sent as form data
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1106,7 +1106,7 @@ class Output(BaseHTML):
|
||||
html_for: Associates the output with one or more elements (by their IDs)
|
||||
form: Associates the output with a form (by id)
|
||||
name: Name of the output element for form submission
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1198,7 +1198,7 @@ class Progress(BaseHTML):
|
||||
form: Associates the progress element with a form (by id)
|
||||
max: Maximum value of the progress indicator
|
||||
value: Current value of the progress indicator
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1306,7 +1306,7 @@ class Select(BaseHTML):
|
||||
required: Indicates that the select control must have a selected option
|
||||
size: Number of visible options in a drop-down list
|
||||
on_change: Fired when the select value changes
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1459,7 +1459,7 @@ class Textarea(BaseHTML):
|
||||
on_blur: Fired when the input loses focus
|
||||
on_key_down: Fired when a key is pressed down
|
||||
on_key_up: Fired when a key is released
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Element classes. This is an auto-generated file. Do not edit. See ../generate.py."""
|
||||
"""Inline classes."""
|
||||
|
||||
from typing import Union
|
||||
|
||||
|
@ -88,7 +88,7 @@ class A(BaseHTML):
|
||||
rel: Specifies the relationship between the linked document and the current document
|
||||
shape: Specifies the shape of the area
|
||||
target: Specifies where to open the linked document
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -174,7 +174,7 @@ class Abbr(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -260,7 +260,7 @@ class B(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -346,7 +346,7 @@ class Bdi(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -432,7 +432,7 @@ class Bdo(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -518,7 +518,7 @@ class Br(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -604,7 +604,7 @@ class Cite(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -690,7 +690,7 @@ class Code(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -778,7 +778,7 @@ class Data(BaseHTML):
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
value: Specifies the machine-readable translation of the data element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -864,7 +864,7 @@ class Dfn(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -950,7 +950,7 @@ class Em(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1036,7 +1036,7 @@ class I(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1122,7 +1122,7 @@ class Kbd(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1208,7 +1208,7 @@ class Mark(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1296,7 +1296,7 @@ class Q(BaseHTML):
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
cite: Specifies the source URL of the quote.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1382,7 +1382,7 @@ class Rp(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1468,7 +1468,7 @@ class Rt(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1554,7 +1554,7 @@ class Ruby(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1640,7 +1640,7 @@ class S(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1726,7 +1726,7 @@ class Samp(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1812,7 +1812,7 @@ class Small(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1898,7 +1898,7 @@ class Span(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1984,7 +1984,7 @@ class Strong(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -2070,7 +2070,7 @@ class Sub(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -2156,7 +2156,7 @@ class Sup(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -2244,7 +2244,7 @@ class Time(BaseHTML):
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
date_time: Specifies the date and/or time of the element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -2330,7 +2330,7 @@ class U(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -2416,7 +2416,7 @@ class Wbr(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Element classes. This is an auto-generated file. Do not edit. See ../generate.py."""
|
||||
"""Media classes."""
|
||||
|
||||
from typing import Any, Union
|
||||
|
||||
@ -129,7 +129,6 @@ class Img(BaseHTML):
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
|
||||
"""
|
||||
return (
|
||||
super().create(src=children[0], **props)
|
||||
@ -274,14 +273,12 @@ class Picture(BaseHTML):
|
||||
"""Display the picture element."""
|
||||
|
||||
tag = "picture"
|
||||
# No unique attributes, only common ones are inherited
|
||||
|
||||
|
||||
class Portal(BaseHTML):
|
||||
"""Display the portal element."""
|
||||
|
||||
tag = "portal"
|
||||
# No unique attributes, only common ones are inherited
|
||||
|
||||
|
||||
class Source(BaseHTML):
|
||||
|
@ -94,7 +94,7 @@ class Area(BaseHTML):
|
||||
rel: Specifies the relationship of the target object to the link object
|
||||
shape: Defines the shape of the area (rectangle, circle, polygon)
|
||||
target: Specifies where to open the linked document
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -198,7 +198,7 @@ class Audio(BaseHTML):
|
||||
muted: Indicates whether the audio is muted by default
|
||||
preload: Specifies how the audio file should be preloaded
|
||||
src: URL of the audio to play
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -314,7 +314,7 @@ class Img(BaseHTML):
|
||||
src: URL of the image to display
|
||||
src_set: A set of source sizes and URLs for responsive images
|
||||
use_map: The name of the map to use with the image
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -340,7 +340,6 @@ class Img(BaseHTML):
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
@ -403,7 +402,7 @@ class Map(BaseHTML):
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
name: Name of the map, referenced by the 'usemap' attribute in 'img' and 'object' elements
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -499,7 +498,7 @@ class Track(BaseHTML):
|
||||
label: Title of the text track, used by the browser when listing available text tracks
|
||||
src: URL of the track file
|
||||
src_lang: Language of the track text data
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -609,7 +608,7 @@ class Video(BaseHTML):
|
||||
poster: URL of an image to show while the video is downloading, or until the user hits the play button
|
||||
preload: Specifies how the video file should be preloaded
|
||||
src: URL of the video to play
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -699,7 +698,7 @@ class Embed(BaseHTML):
|
||||
*children: The children of the component.
|
||||
src: URL of the embedded content
|
||||
type: Media type of the embedded content
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -805,7 +804,7 @@ class Iframe(BaseHTML):
|
||||
sandbox: Security restrictions for the content in the iframe
|
||||
src: URL of the document to display in the iframe
|
||||
src_doc: HTML content to embed directly within the iframe
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -901,7 +900,7 @@ class Object(BaseHTML):
|
||||
name: Name of the object, used for scripting or as a target for forms and links
|
||||
type: Media type of the data specified in the data attribute
|
||||
use_map: Name of an image map to use with the object
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -987,7 +986,7 @@ class Picture(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: No unique attributes, only common ones are inherited Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1073,7 +1072,7 @@ class Portal(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: No unique attributes, only common ones are inherited Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1169,7 +1168,7 @@ class Source(BaseHTML):
|
||||
src: URL of the media file or an image for the element to use
|
||||
src_set: A set of source sizes and URLs for responsive images
|
||||
type: Media type of the source
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1261,7 +1260,7 @@ class Svg(BaseHTML):
|
||||
width: The width of the svg.
|
||||
height: The height of the svg.
|
||||
xmlns: The XML namespace declaration.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1361,7 +1360,7 @@ class Text(BaseHTML):
|
||||
rotate: Rotates orientation of each individual glyph.
|
||||
length_adjust: How the text is stretched or compressed to fit the width defined by the text_length attribute.
|
||||
text_length: A width that the text should be scaled to fit.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1457,7 +1456,7 @@ class Line(BaseHTML):
|
||||
y1: The y-axis coordinate of the line starting point.
|
||||
y2: The y-axis coordinate of the the line ending point.
|
||||
path_length: The total path length, in user units.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1551,7 +1550,7 @@ class Circle(BaseHTML):
|
||||
cy: The y-axis coordinate of the center of the circle.
|
||||
r: The radius of the circle.
|
||||
path_length: The total length for the circle's circumference, in user units.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1647,7 +1646,7 @@ class Ellipse(BaseHTML):
|
||||
rx: The radius of the ellipse on the x axis.
|
||||
ry: The radius of the ellipse on the y axis.
|
||||
path_length: The total length for the ellipse's circumference, in user units.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1747,7 +1746,7 @@ class Rect(BaseHTML):
|
||||
rx: The horizontal corner radius of the rect. Defaults to ry if it is specified.
|
||||
ry: The vertical corner radius of the rect. Defaults to rx if it is specified.
|
||||
path_length: The total length of the rectangle's perimeter, in user units.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1837,7 +1836,7 @@ class Polygon(BaseHTML):
|
||||
*children: The children of the component.
|
||||
points: defines the list of points (pairs of x,y absolute coordinates) required to draw the polygon.
|
||||
path_length: This prop lets specify the total length for the path, in user units.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -1923,7 +1922,7 @@ class Defs(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -2023,7 +2022,7 @@ class LinearGradient(BaseHTML):
|
||||
x2: X coordinate of the ending point of the gradient.
|
||||
y1: Y coordinate of the starting point of the gradient.
|
||||
y2: Y coordinate of the ending point of the gradient.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -2127,7 +2126,7 @@ class RadialGradient(BaseHTML):
|
||||
gradient_transform: Transform applied to the gradient.
|
||||
r: The radius of the end circle of the radial gradient.
|
||||
spread_method: Method used to spread the gradient.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -2223,7 +2222,7 @@ class Stop(BaseHTML):
|
||||
offset: Offset of the gradient stop.
|
||||
stop_color: Color of the gradient stop.
|
||||
stop_opacity: Opacity of the gradient stop.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -2311,7 +2310,7 @@ class Path(BaseHTML):
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
d: Defines the shape of the path.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -2413,7 +2412,7 @@ class SVG(ComponentNamespace):
|
||||
width: The width of the svg.
|
||||
height: The height of the svg.
|
||||
xmlns: The XML namespace declaration.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Element classes. This is an auto-generated file. Do not edit. See ../generate.py."""
|
||||
"""Metadata classes."""
|
||||
|
||||
from typing import List, Union
|
||||
|
||||
@ -81,7 +81,7 @@ class Title(Element):
|
||||
tag = "title"
|
||||
|
||||
|
||||
# Had to be named with an underscore so it doesnt conflict with reflex.style Style in pyi
|
||||
# Had to be named with an underscore so it doesn't conflict with reflex.style Style in pyi
|
||||
class StyleEl(Element):
|
||||
"""Display the style element."""
|
||||
|
||||
|
@ -71,7 +71,7 @@ class Base(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -157,7 +157,7 @@ class Head(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -265,7 +265,7 @@ class Link(BaseHTML):
|
||||
rel: Specifies the relationship between the current document and the linked one
|
||||
sizes: Specifies the sizes of icons for visual media
|
||||
type: Specifies the MIME type of the linked document
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
@ -359,7 +359,7 @@ class Meta(BaseHTML):
|
||||
content: Defines the content of the metadata
|
||||
http_equiv: Provides an HTTP header for the information/value of the content attribute
|
||||
name: Specifies a name for the metadata
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Element classes. This is an auto-generated file. Do not edit. See ../generate.py."""
|
||||
"""Other classes."""
|
||||
|
||||
from typing import Union
|
||||
|
||||
@ -26,31 +26,39 @@ class Dialog(BaseHTML):
|
||||
|
||||
|
||||
class Summary(BaseHTML):
|
||||
"""Display the summary element."""
|
||||
"""Display the summary element.
|
||||
|
||||
Used as a summary or caption for a <details> element.
|
||||
"""
|
||||
|
||||
tag = "summary"
|
||||
# No unique attributes, only common ones are inherited; used as a summary or caption for a <details> element
|
||||
|
||||
|
||||
class Slot(BaseHTML):
|
||||
"""Display the slot element."""
|
||||
"""Display the slot element.
|
||||
|
||||
Used as a placeholder inside a web component.
|
||||
"""
|
||||
|
||||
tag = "slot"
|
||||
# No unique attributes, only common ones are inherited; used as a placeholder inside a web component
|
||||
|
||||
|
||||
class Template(BaseHTML):
|
||||
"""Display the template element."""
|
||||
"""Display the template element.
|
||||
|
||||
Used for declaring fragments of HTML that can be cloned and inserted in the document.
|
||||
"""
|
||||
|
||||
tag = "template"
|
||||
# No unique attributes, only common ones are inherited; used for declaring fragments of HTML that can be cloned and inserted in the document
|
||||
|
||||
|
||||
class Math(BaseHTML):
|
||||
"""Display the math element."""
|
||||
"""Display the math element.
|
||||
|
||||
Represents a mathematical expression.
|
||||
"""
|
||||
|
||||
tag = "math"
|
||||
# No unique attributes, only common ones are inherited; used for displaying mathematical expressions
|
||||
|
||||
|
||||
class Html(BaseHTML):
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user