Merge branch 'main' into alek/fixpin
This commit is contained in:
commit
34d4602087
14
.github/workflows/benchmarks.yml
vendored
14
.github/workflows/benchmarks.yml
vendored
@ -81,15 +81,13 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
# Show OS combos first in GUI
|
# Show OS combos first in GUI
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8"]
|
python-version: ["3.10.16", "3.11.11", "3.12.8"]
|
||||||
exclude:
|
exclude:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.10.16"
|
python-version: "3.10.16"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.9.21"
|
python-version: "3.11.11"
|
||||||
# keep only one python version for MacOS
|
# keep only one python version for MacOS
|
||||||
- os: macos-latest
|
|
||||||
python-version: "3.9.21"
|
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
python-version: "3.10.16"
|
python-version: "3.10.16"
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
@ -98,7 +96,7 @@ jobs:
|
|||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.10.11"
|
python-version: "3.10.11"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.9.13"
|
python-version: "3.11.9"
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
@ -161,7 +159,11 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- 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
|
- name: Install Poetry
|
||||||
uses: snok/install-poetry@v1
|
uses: snok/install-poetry@v1
|
||||||
with:
|
with:
|
||||||
|
@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
- uses: ./.github/actions/setup_build_env
|
||||||
with:
|
with:
|
||||||
python-version: "3.9.21"
|
python-version: '3.10'
|
||||||
run-poetry-install: true
|
run-poetry-install: true
|
||||||
create-venv-at-path: .venv
|
create-venv-at-path: .venv
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ jobs:
|
|||||||
path: reflex-web
|
path: reflex-web
|
||||||
- name: Install Requirements for reflex-web
|
- name: Install Requirements for reflex-web
|
||||||
working-directory: ./reflex-web
|
working-directory: ./reflex-web
|
||||||
run: poetry run uv pip install -r requirements.txt
|
run: poetry run uv pip install $(grep -ivE "reflex " requirements.txt)
|
||||||
- name: Install additional dependencies for DB access
|
- name: Install additional dependencies for DB access
|
||||||
run: poetry run uv pip install psycopg
|
run: poetry run uv pip install psycopg
|
||||||
- name: Init Website for reflex-web
|
- name: Init Website for reflex-web
|
||||||
@ -73,7 +73,7 @@ jobs:
|
|||||||
echo "$outdated"
|
echo "$outdated"
|
||||||
|
|
||||||
# Ignore 3rd party dependencies that are not updated.
|
# 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)
|
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)
|
no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
|
||||||
|
|
||||||
|
|
||||||
|
11
.github/workflows/integration_app_harness.yml
vendored
11
.github/workflows/integration_app_harness.yml
vendored
@ -47,17 +47,10 @@ jobs:
|
|||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
run-poetry-install: true
|
run-poetry-install: true
|
||||||
create-venv-at-path: .venv
|
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
|
- name: Run app harness tests
|
||||||
env:
|
env:
|
||||||
SCREENSHOT_DIR: /tmp/screenshots/${{ matrix.state_manager }}/${{ matrix.python-version }}/${{ matrix.split_index }}
|
|
||||||
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
|
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
|
||||||
run: |
|
run: |
|
||||||
poetry run playwright install chromium
|
poetry run playwright install chromium
|
||||||
poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}}
|
poetry run pytest tests/integration --retries 3 --maxfail=5 --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
|
|
||||||
|
33
.github/workflows/integration_tests.yml
vendored
33
.github/workflows/integration_tests.yml
vendored
@ -33,7 +33,7 @@ env:
|
|||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
example-counter:
|
example-counter-and-nba-proxy:
|
||||||
env:
|
env:
|
||||||
OUTPUT_FILE: import_benchmark.json
|
OUTPUT_FILE: import_benchmark.json
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
@ -43,22 +43,17 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
# Show OS combos first in GUI
|
# Show OS combos first in GUI
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
|
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:
|
exclude:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.11.11"
|
python-version: "3.11.11"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.10.16"
|
python-version: '3.10.16'
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.9.21"
|
|
||||||
include:
|
include:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.11.9"
|
python-version: "3.11.9"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.10.11"
|
python-version: '3.10.11'
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.9.13"
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
@ -119,6 +114,26 @@ jobs:
|
|||||||
--benchmark-json "./reflex-examples/counter/${{ env.OUTPUT_FILE }}"
|
--benchmark-json "./reflex-examples/counter/${{ env.OUTPUT_FILE }}"
|
||||||
--branch-name "${{ github.head_ref || github.ref_name }}" --pr-id "${{ github.event.pull_request.id }}"
|
--branch-name "${{ github.head_ref || github.ref_name }}" --pr-id "${{ github.event.pull_request.id }}"
|
||||||
--app-name "counter"
|
--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:
|
reflex-web:
|
||||||
strategy:
|
strategy:
|
||||||
|
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
|
10
.github/workflows/unit_tests.yml
vendored
10
.github/workflows/unit_tests.yml
vendored
@ -28,22 +28,18 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
|
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
|
# Windows is a bit behind on Python version availability in Github
|
||||||
exclude:
|
exclude:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.11.11"
|
python-version: "3.11.11"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.10.16"
|
python-version: "3.10.16"
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.9.21"
|
|
||||||
include:
|
include:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.11.9"
|
python-version: "3.11.9"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.10.11"
|
python-version: "3.10.11"
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.9.13"
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
# Service containers to run with `runner-job`
|
# Service containers to run with `runner-job`
|
||||||
@ -92,8 +88,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# Note: py39, py310, py311 versions chosen due to available arm64 darwin builds.
|
# Note: py310, py311 versions chosen due to available arm64 darwin builds.
|
||||||
python-version: ["3.9.13", "3.10.11", "3.11.9", "3.12.8", "3.13.1"]
|
python-version: ["3.10.11", "3.11.9", "3.12.8", "3.13.1"]
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,6 +4,7 @@ assets/external/*
|
|||||||
dist/*
|
dist/*
|
||||||
examples/
|
examples/
|
||||||
.web
|
.web
|
||||||
|
.states
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
.coverage
|
.coverage
|
||||||
@ -14,3 +15,4 @@ requirements.txt
|
|||||||
.pyi_generator_last_run
|
.pyi_generator_last_run
|
||||||
.pyi_generator_diff
|
.pyi_generator_diff
|
||||||
reflex.db
|
reflex.db
|
||||||
|
.codspeed
|
@ -3,7 +3,7 @@ fail_fast: true
|
|||||||
repos:
|
repos:
|
||||||
|
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
rev: v0.8.2
|
rev: v0.9.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
args: [reflex, tests]
|
args: [reflex, tests]
|
||||||
@ -24,11 +24,12 @@ repos:
|
|||||||
name: update-pyi-files
|
name: update-pyi-files
|
||||||
always_run: true
|
always_run: true
|
||||||
language: system
|
language: system
|
||||||
|
require_serial: true
|
||||||
description: 'Update pyi files as needed'
|
description: 'Update pyi files as needed'
|
||||||
entry: python3 scripts/make_pyi.py
|
entry: python3 scripts/make_pyi.py
|
||||||
|
|
||||||
- repo: https://github.com/RobertCraigie/pyright-python
|
- repo: https://github.com/RobertCraigie/pyright-python
|
||||||
rev: v1.1.313
|
rev: v1.1.393
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyright
|
- id: pyright
|
||||||
args: [reflex, tests]
|
args: [reflex, tests]
|
||||||
|
@ -8,7 +8,7 @@ Here is a quick guide on how to run Reflex repo locally so you can start contrib
|
|||||||
|
|
||||||
**Prerequisites:**
|
**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).
|
- 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:**
|
**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.
|
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
|
``` bash
|
||||||
pre-commit install
|
pre-commit install
|
||||||
|
@ -34,7 +34,7 @@ See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architectu
|
|||||||
|
|
||||||
## ⚙️ Installation
|
## ⚙️ Installation
|
||||||
|
|
||||||
Open a terminal and run (Requires Python 3.9+):
|
Open a terminal and run (Requires Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -21,7 +21,7 @@ def get_package_size(venv_path: Path, os_name):
|
|||||||
ValueError: when venv does not exist or python version is None.
|
ValueError: when venv does not exist or python version is None.
|
||||||
"""
|
"""
|
||||||
python_version = get_python_version(venv_path, os_name)
|
python_version = get_python_version(venv_path, os_name)
|
||||||
print("Python version:", python_version) # noqa: T201
|
print("Python version:", python_version)
|
||||||
if python_version is None:
|
if python_version is None:
|
||||||
raise ValueError("Error: Failed to determine Python version.")
|
raise ValueError("Error: Failed to determine Python version.")
|
||||||
|
|
||||||
|
@ -34,13 +34,13 @@ def render_component(num: int):
|
|||||||
rx.box(
|
rx.box(
|
||||||
rx.accordion.root(
|
rx.accordion.root(
|
||||||
rx.accordion.item(
|
rx.accordion.item(
|
||||||
header="Full Ingredients", # type: ignore
|
header="Full Ingredients",
|
||||||
content="Yes. It's built with accessibility in mind.", # type: ignore
|
content="Yes. It's built with accessibility in mind.",
|
||||||
font_size="3em",
|
font_size="3em",
|
||||||
),
|
),
|
||||||
rx.accordion.item(
|
rx.accordion.item(
|
||||||
header="Applications", # type: ignore
|
header="Applications",
|
||||||
content="Yes. It's unstyled by default, giving you freedom over the look and feel.", # type: ignore
|
content="Yes. It's unstyled by default, giving you freedom over the look and feel.",
|
||||||
),
|
),
|
||||||
collapsible=True,
|
collapsible=True,
|
||||||
variant="ghost",
|
variant="ghost",
|
||||||
@ -122,7 +122,7 @@ def AppWithTenComponentsOnePage():
|
|||||||
def index() -> rx.Component:
|
def index() -> rx.Component:
|
||||||
return rx.center(rx.vstack(*render_component(1)))
|
return rx.center(rx.vstack(*render_component(1)))
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
app.add_page(index)
|
app.add_page(index)
|
||||||
|
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ def AppWithHundredComponentOnePage():
|
|||||||
def index() -> rx.Component:
|
def index() -> rx.Component:
|
||||||
return rx.center(rx.vstack(*render_component(100)))
|
return rx.center(rx.vstack(*render_component(100)))
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
app.add_page(index)
|
app.add_page(index)
|
||||||
|
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ def AppWithThousandComponentsOnePage():
|
|||||||
def index() -> rx.Component:
|
def index() -> rx.Component:
|
||||||
return rx.center(rx.vstack(*render_component(1000)))
|
return rx.center(rx.vstack(*render_component(1000)))
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
app.add_page(index)
|
app.add_page(index)
|
||||||
|
|
||||||
|
|
||||||
@ -166,9 +166,9 @@ def app_with_10_components(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithTenComponentsOnePage,
|
AppWithTenComponentsOnePage,
|
||||||
render_component=render_component, # type: ignore
|
render_component=render_component, # pyright: ignore [reportCallIssue]
|
||||||
),
|
),
|
||||||
) # type: ignore
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
@ -189,9 +189,9 @@ def app_with_100_components(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithHundredComponentOnePage,
|
AppWithHundredComponentOnePage,
|
||||||
render_component=render_component, # type: ignore
|
render_component=render_component, # pyright: ignore [reportCallIssue]
|
||||||
),
|
),
|
||||||
) # type: ignore
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
@ -212,9 +212,9 @@ def app_with_1000_components(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithThousandComponentsOnePage,
|
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)
|
@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 typing import Tuple
|
||||||
|
|
||||||
from rxconfig import config # type: ignore
|
from rxconfig import config # pyright: ignore [reportMissingImports]
|
||||||
|
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
@ -74,13 +74,13 @@ def render_multiple_pages(app, num: int):
|
|||||||
rx.select(
|
rx.select(
|
||||||
["C", "PF", "SF", "PG", "SG"],
|
["C", "PF", "SF", "PG", "SG"],
|
||||||
placeholder="Select a position. (All)",
|
placeholder="Select a position. (All)",
|
||||||
on_change=State.set_position, # type: ignore
|
on_change=State.set_position, # pyright: ignore [reportAttributeAccessIssue]
|
||||||
size="3",
|
size="3",
|
||||||
),
|
),
|
||||||
rx.select(
|
rx.select(
|
||||||
college,
|
college,
|
||||||
placeholder="Select a college. (All)",
|
placeholder="Select a college. (All)",
|
||||||
on_change=State.set_college, # type: ignore
|
on_change=State.set_college, # pyright: ignore [reportAttributeAccessIssue]
|
||||||
size="3",
|
size="3",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -95,7 +95,7 @@ def render_multiple_pages(app, num: int):
|
|||||||
default_value=[18, 50],
|
default_value=[18, 50],
|
||||||
min=18,
|
min=18,
|
||||||
max=50,
|
max=50,
|
||||||
on_value_commit=State.set_age, # type: ignore
|
on_value_commit=State.set_age, # pyright: ignore [reportAttributeAccessIssue]
|
||||||
),
|
),
|
||||||
align_items="left",
|
align_items="left",
|
||||||
width="100%",
|
width="100%",
|
||||||
@ -110,7 +110,7 @@ def render_multiple_pages(app, num: int):
|
|||||||
default_value=[0, 25000000],
|
default_value=[0, 25000000],
|
||||||
min=0,
|
min=0,
|
||||||
max=25000000,
|
max=25000000,
|
||||||
on_value_commit=State.set_salary, # type: ignore
|
on_value_commit=State.set_salary, # pyright: ignore [reportAttributeAccessIssue]
|
||||||
),
|
),
|
||||||
align_items="left",
|
align_items="left",
|
||||||
width="100%",
|
width="100%",
|
||||||
@ -130,7 +130,7 @@ def render_multiple_pages(app, num: int):
|
|||||||
|
|
||||||
def AppWithOnePage():
|
def AppWithOnePage():
|
||||||
"""A reflex app with one page."""
|
"""A reflex app with one page."""
|
||||||
from rxconfig import config # type: ignore
|
from rxconfig import config # pyright: ignore [reportMissingImports]
|
||||||
|
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ def AppWithOnePage():
|
|||||||
height="100vh",
|
height="100vh",
|
||||||
)
|
)
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
app.add_page(index)
|
app.add_page(index)
|
||||||
|
|
||||||
|
|
||||||
@ -170,7 +170,7 @@ def AppWithTenPages():
|
|||||||
"""A reflex app with 10 pages."""
|
"""A reflex app with 10 pages."""
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
render_multiple_pages(app, 10)
|
render_multiple_pages(app, 10)
|
||||||
|
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ def AppWithHundredPages():
|
|||||||
"""A reflex app with 100 pages."""
|
"""A reflex app with 100 pages."""
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
render_multiple_pages(app, 100)
|
render_multiple_pages(app, 100)
|
||||||
|
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ def AppWithThousandPages():
|
|||||||
"""A reflex app with Thousand pages."""
|
"""A reflex app with Thousand pages."""
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
render_multiple_pages(app, 1000)
|
render_multiple_pages(app, 1000)
|
||||||
|
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ def AppWithTenThousandPages():
|
|||||||
"""A reflex app with ten thousand pages."""
|
"""A reflex app with ten thousand pages."""
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
render_multiple_pages(app, 10000)
|
render_multiple_pages(app, 10000)
|
||||||
|
|
||||||
|
|
||||||
@ -232,7 +232,7 @@ def app_with_ten_pages(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithTenPages,
|
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,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithHundredPages,
|
AppWithHundredPages,
|
||||||
render_comp=render_multiple_pages, # type: ignore
|
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||||
),
|
),
|
||||||
) # type: ignore
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
@ -278,9 +278,9 @@ def app_with_thousand_pages(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithThousandPages,
|
AppWithThousandPages,
|
||||||
render_comp=render_multiple_pages, # type: ignore
|
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||||
),
|
),
|
||||||
) # type: ignore
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
@ -301,9 +301,9 @@ def app_with_ten_thousand_pages(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithTenThousandPages,
|
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)
|
@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()
|
@ -34,7 +34,7 @@ Auf unserer [Architektur-Seite](https://reflex.dev/blog/2024-03-21-reflex-archit
|
|||||||
|
|
||||||
## ⚙️ Installation
|
## ⚙️ 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
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -35,7 +35,7 @@ Consulta nuestra [página de arquitectura](https://reflex.dev/blog/2024-03-21-re
|
|||||||
|
|
||||||
## ⚙️ Instalación
|
## ⚙️ Instalación
|
||||||
|
|
||||||
Abra un terminal y ejecute (Requiere Python 3.9+):
|
Abra un terminal y ejecute (Requiere Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -35,7 +35,7 @@ Reflex के अंदर के कामकाज को जानने क
|
|||||||
|
|
||||||
## ⚙️ इंस्टॉलेशन (Installation)
|
## ⚙️ इंस्टॉलेशन (Installation)
|
||||||
|
|
||||||
एक टर्मिनल खोलें और चलाएं (Python 3.9+ की आवश्यकता है):
|
एक टर्मिनल खोलें और चलाएं (Python 3.10+ की आवश्यकता है):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
## ⚙️ Installazione
|
## ⚙️ Installazione
|
||||||
|
|
||||||
Apri un terminale ed esegui (Richiede Python 3.9+):
|
Apri un terminale ed esegui (Richiede Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -37,7 +37,7 @@ Reflex がどのように動作しているかを知るには、[アーキテク
|
|||||||
|
|
||||||
## ⚙️ インストール
|
## ⚙️ インストール
|
||||||
|
|
||||||
ターミナルを開いて以下のコマンドを実行してください。(Python 3.9 以上が必要です。):
|
ターミナルを開いて以下のコマンドを実行してください。(Python 3.10 以上が必要です。):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
---
|
---
|
||||||
## ⚙️ 설치
|
## ⚙️ 설치
|
||||||
|
|
||||||
터미널을 열고 실행하세요. (Python 3.9+ 필요):
|
터미널을 열고 실행하세요. (Python 3.10+ 필요):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
## ⚙️ Installation - نصب و راه اندازی
|
## ⚙️ Installation - نصب و راه اندازی
|
||||||
|
|
||||||
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.9+):
|
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
---
|
---
|
||||||
## ⚙️ Instalação
|
## ⚙️ Instalação
|
||||||
|
|
||||||
Abra um terminal e execute (Requer Python 3.9+):
|
Abra um terminal e execute (Requer Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
## ⚙️ Kurulum
|
## ⚙️ 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
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -34,7 +34,7 @@ Các tính năng chính:
|
|||||||
|
|
||||||
## ⚙️ Cài đặt
|
## ⚙️ 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
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -34,7 +34,7 @@ Reflex 是一个使用纯Python构建全栈web应用的库。
|
|||||||
|
|
||||||
## ⚙️ 安装
|
## ⚙️ 安装
|
||||||
|
|
||||||
打开一个终端并且运行(要求Python3.9+):
|
打开一个终端并且运行(要求Python3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -36,7 +36,7 @@ Reflex 是一個可以用純 Python 構建全端網頁應用程式的函式庫
|
|||||||
|
|
||||||
## ⚙️ 安裝
|
## ⚙️ 安裝
|
||||||
|
|
||||||
開啟一個終端機並且執行 (需要 Python 3.9+):
|
開啟一個終端機並且執行 (需要 Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
643
poetry.lock
generated
643
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,7 @@ keywords = ["web", "framework"]
|
|||||||
classifiers = ["Development Status :: 4 - Beta"]
|
classifiers = ["Development Status :: 4 - Beta"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = ">=3.10, <4.0"
|
||||||
fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
|
fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
|
||||||
gunicorn = ">=20.1.0,<24.0"
|
gunicorn = ">=20.1.0,<24.0"
|
||||||
jinja2 = ">=3.1.2,<4.0"
|
jinja2 = ">=3.1.2,<4.0"
|
||||||
@ -50,19 +50,18 @@ httpx = ">=0.25.1,<1.0"
|
|||||||
twine = ">=4.0.0,<7.0"
|
twine = ">=4.0.0,<7.0"
|
||||||
tomlkit = ">=0.12.4,<1.0"
|
tomlkit = ">=0.12.4,<1.0"
|
||||||
lazy_loader = ">=0.4"
|
lazy_loader = ">=0.4"
|
||||||
reflex-chakra = ">=0.6.0"
|
|
||||||
typing_extensions = ">=4.6.0"
|
typing_extensions = ">=4.6.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pytest = ">=7.1.2,<9.0"
|
pytest = ">=7.1.2,<9.0"
|
||||||
pytest-mock = ">=3.10.0,<4.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"
|
darglint = ">=1.8.1,<2.0"
|
||||||
dill = ">=0.3.8"
|
dill = ">=0.3.8"
|
||||||
toml = ">=0.10.2,<1.0"
|
toml = ">=0.10.2,<1.0"
|
||||||
pytest-asyncio = ">=0.24.0"
|
pytest-asyncio = ">=0.24.0"
|
||||||
pytest-cov = ">=4.0.0,<7.0"
|
pytest-cov = ">=4.0.0,<7.0"
|
||||||
ruff = "0.8.2"
|
ruff = "0.9.3"
|
||||||
pandas = ">=2.1.1,<3.0"
|
pandas = ">=2.1.1,<3.0"
|
||||||
pillow = ">=10.0.0,<12.0"
|
pillow = ">=10.0.0,<12.0"
|
||||||
plotly = ">=5.13.0,<6.0"
|
plotly = ">=5.13.0,<6.0"
|
||||||
@ -72,6 +71,7 @@ selenium = ">=4.11.0,<5.0"
|
|||||||
pytest-benchmark = ">=4.0.0,<6.0"
|
pytest-benchmark = ">=4.0.0,<6.0"
|
||||||
playwright = ">=1.46.0"
|
playwright = ">=1.46.0"
|
||||||
pytest-playwright = ">=0.5.1"
|
pytest-playwright = ">=0.5.1"
|
||||||
|
pytest-codspeed = "^3.1.2"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
reflex = "reflex.reflex:cli"
|
reflex = "reflex.reflex:cli"
|
||||||
@ -81,20 +81,24 @@ requires = ["poetry-core>=1.5.1"]
|
|||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
|
reportIncompatibleMethodOverride = false
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
target-version = "py39"
|
target-version = "py310"
|
||||||
output-format = "concise"
|
output-format = "concise"
|
||||||
lint.isort.split-on-trailing-comma = false
|
lint.isort.split-on-trailing-comma = false
|
||||||
lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "PERF", "PTH", "RUF", "SIM", "T", "W"]
|
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", "RUF012"]
|
lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF008", "RUF012", "TRY0"]
|
||||||
lint.pydocstyle.convention = "google"
|
lint.pydocstyle.convention = "google"
|
||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"__init__.py" = ["F401"]
|
"__init__.py" = ["F401"]
|
||||||
"tests/*.py" = ["D100", "D103", "D104", "B018", "PERF", "T"]
|
"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"]
|
"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"]
|
"*/blank.py" = ["I001"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
@ -102,5 +106,5 @@ asyncio_default_fixture_loop_scope = "function"
|
|||||||
asyncio_mode = "auto"
|
asyncio_mode = "auto"
|
||||||
|
|
||||||
[tool.codespell]
|
[tool.codespell]
|
||||||
skip = "docs/*,*.html,examples/*, *.pyi"
|
skip = "docs/*,*.html,examples/*, *.pyi, poetry.lock"
|
||||||
ignore-words-list = "te, TreeE"
|
ignore-words-list = "te, TreeE"
|
||||||
|
@ -8,7 +8,7 @@ version = "0.0.1"
|
|||||||
description = "Reflex custom component {{ module_name }}"
|
description = "Reflex custom component {{ module_name }}"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { text = "Apache-2.0" }
|
license = { text = "Apache-2.0" }
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.10"
|
||||||
authors = [{ name = "", email = "YOUREMAIL@domain.com" }]
|
authors = [{ name = "", email = "YOUREMAIL@domain.com" }]
|
||||||
keywords = ["reflex","reflex-custom-components"]
|
keywords = ["reflex","reflex-custom-components"]
|
||||||
|
|
||||||
|
@ -38,13 +38,13 @@ export default function MyApp({ Component, pageProps }) {
|
|||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<ThemeProvider defaultTheme={ defaultColorMode } attribute="class">
|
<ThemeProvider defaultTheme={ defaultColorMode } attribute="class">
|
||||||
<AppWrap>
|
<StateProvider>
|
||||||
<StateProvider>
|
<EventLoopProvider>
|
||||||
<EventLoopProvider>
|
<AppWrap>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</EventLoopProvider>
|
</AppWrap>
|
||||||
</StateProvider>
|
</EventLoopProvider>
|
||||||
</AppWrap>
|
</StateProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -86,11 +86,11 @@
|
|||||||
{% for condition in case[:-1] %}
|
{% for condition in case[:-1] %}
|
||||||
case JSON.stringify({{ condition._js_expr }}):
|
case JSON.stringify({{ condition._js_expr }}):
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
return {{ case[-1] }};
|
return {{ render(case[-1]) }};
|
||||||
break;
|
break;
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
default:
|
default:
|
||||||
return {{ component.default }};
|
return {{ render(component.default) }};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
@ -16,10 +16,7 @@ export default function RadixThemesColorModeProvider({ children }) {
|
|||||||
if (isDevMode) {
|
if (isDevMode) {
|
||||||
const lastCompiledTimeInLocalStorage =
|
const lastCompiledTimeInLocalStorage =
|
||||||
localStorage.getItem("last_compiled_time");
|
localStorage.getItem("last_compiled_time");
|
||||||
if (
|
if (lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp) {
|
||||||
lastCompiledTimeInLocalStorage &&
|
|
||||||
lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp
|
|
||||||
) {
|
|
||||||
// on app startup, make sure the application color mode is persisted correctly.
|
// on app startup, make sure the application color mode is persisted correctly.
|
||||||
setTheme(defaultColorMode);
|
setTheme(defaultColorMode);
|
||||||
localStorage.setItem("last_compiled_time", lastCompiledTimeStamp);
|
localStorage.setItem("last_compiled_time", lastCompiledTimeStamp);
|
||||||
|
@ -3,6 +3,7 @@ import axios from "axios";
|
|||||||
import io from "socket.io-client";
|
import io from "socket.io-client";
|
||||||
import JSON5 from "json5";
|
import JSON5 from "json5";
|
||||||
import env from "$/env.json";
|
import env from "$/env.json";
|
||||||
|
import reflexEnvironment from "$/reflex.json";
|
||||||
import Cookies from "universal-cookie";
|
import Cookies from "universal-cookie";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import Router, { useRouter } from "next/router";
|
import Router, { useRouter } from "next/router";
|
||||||
@ -105,6 +106,18 @@ export const getBackendURL = (url_str) => {
|
|||||||
return endpoint;
|
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.
|
* Determine if any event in the event queue is stateful.
|
||||||
*
|
*
|
||||||
@ -214,8 +227,8 @@ export const applyEvent = async (event, socket) => {
|
|||||||
a.href = eval?.(
|
a.href = eval?.(
|
||||||
event.payload.url.replace(
|
event.payload.url.replace(
|
||||||
"getBackendURL(env.UPLOAD)",
|
"getBackendURL(env.UPLOAD)",
|
||||||
`"${getBackendURL(env.UPLOAD)}"`
|
`"${getBackendURL(env.UPLOAD)}"`,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
a.download = event.payload.filename;
|
a.download = event.payload.filename;
|
||||||
@ -300,10 +313,7 @@ export const applyEvent = async (event, socket) => {
|
|||||||
|
|
||||||
// Send the event to the server.
|
// Send the event to the server.
|
||||||
if (socket) {
|
if (socket) {
|
||||||
socket.emit(
|
socket.emit("event", event);
|
||||||
"event",
|
|
||||||
event,
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,7 +341,7 @@ export const applyRestEvent = async (event, socket) => {
|
|||||||
event.payload.files,
|
event.payload.files,
|
||||||
event.payload.upload_id,
|
event.payload.upload_id,
|
||||||
event.payload.on_upload_progress,
|
event.payload.on_upload_progress,
|
||||||
socket
|
socket,
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -398,7 +408,7 @@ export const connect = async (
|
|||||||
dispatch,
|
dispatch,
|
||||||
transports,
|
transports,
|
||||||
setConnectErrors,
|
setConnectErrors,
|
||||||
client_storage = {}
|
client_storage = {},
|
||||||
) => {
|
) => {
|
||||||
// Get backend URL object from the endpoint.
|
// Get backend URL object from the endpoint.
|
||||||
const endpoint = getBackendURL(EVENTURL);
|
const endpoint = getBackendURL(EVENTURL);
|
||||||
@ -407,6 +417,7 @@ export const connect = async (
|
|||||||
socket.current = io(endpoint.href, {
|
socket.current = io(endpoint.href, {
|
||||||
path: endpoint["pathname"],
|
path: endpoint["pathname"],
|
||||||
transports: transports,
|
transports: transports,
|
||||||
|
protocols: [reflexEnvironment.version],
|
||||||
autoUnref: false,
|
autoUnref: false,
|
||||||
});
|
});
|
||||||
// Ensure undefined fields in events are sent as null instead of removed
|
// Ensure undefined fields in events are sent as null instead of removed
|
||||||
@ -488,14 +499,14 @@ export const uploadFiles = async (
|
|||||||
files,
|
files,
|
||||||
upload_id,
|
upload_id,
|
||||||
on_upload_progress,
|
on_upload_progress,
|
||||||
socket
|
socket,
|
||||||
) => {
|
) => {
|
||||||
// return if there's no file to upload
|
// return if there's no file to upload
|
||||||
if (files === undefined || files.length === 0) {
|
if (files === undefined || files.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const upload_ref_name = `__upload_controllers_${upload_id}`
|
const upload_ref_name = `__upload_controllers_${upload_id}`;
|
||||||
|
|
||||||
if (refs[upload_ref_name]) {
|
if (refs[upload_ref_name]) {
|
||||||
console.log("Upload already in progress for ", upload_id);
|
console.log("Upload already in progress for ", upload_id);
|
||||||
@ -593,7 +604,7 @@ export const Event = (
|
|||||||
name,
|
name,
|
||||||
payload = {},
|
payload = {},
|
||||||
event_actions = {},
|
event_actions = {},
|
||||||
handler = null
|
handler = null,
|
||||||
) => {
|
) => {
|
||||||
return { name, payload, handler, event_actions };
|
return { name, payload, handler, event_actions };
|
||||||
};
|
};
|
||||||
@ -620,7 +631,7 @@ export const hydrateClientStorage = (client_storage) => {
|
|||||||
for (const state_key in client_storage.local_storage) {
|
for (const state_key in client_storage.local_storage) {
|
||||||
const options = client_storage.local_storage[state_key];
|
const options = client_storage.local_storage[state_key];
|
||||||
const local_storage_value = localStorage.getItem(
|
const local_storage_value = localStorage.getItem(
|
||||||
options.name || state_key
|
options.name || state_key,
|
||||||
);
|
);
|
||||||
if (local_storage_value !== null) {
|
if (local_storage_value !== null) {
|
||||||
client_storage_values[state_key] = local_storage_value;
|
client_storage_values[state_key] = local_storage_value;
|
||||||
@ -631,7 +642,7 @@ export const hydrateClientStorage = (client_storage) => {
|
|||||||
for (const state_key in client_storage.session_storage) {
|
for (const state_key in client_storage.session_storage) {
|
||||||
const session_options = client_storage.session_storage[state_key];
|
const session_options = client_storage.session_storage[state_key];
|
||||||
const session_storage_value = sessionStorage.getItem(
|
const session_storage_value = sessionStorage.getItem(
|
||||||
session_options.name || state_key
|
session_options.name || state_key,
|
||||||
);
|
);
|
||||||
if (session_storage_value != null) {
|
if (session_storage_value != null) {
|
||||||
client_storage_values[state_key] = session_storage_value;
|
client_storage_values[state_key] = session_storage_value;
|
||||||
@ -656,7 +667,7 @@ export const hydrateClientStorage = (client_storage) => {
|
|||||||
const applyClientStorageDelta = (client_storage, delta) => {
|
const applyClientStorageDelta = (client_storage, delta) => {
|
||||||
// find the main state and check for is_hydrated
|
// find the main state and check for is_hydrated
|
||||||
const unqualified_states = Object.keys(delta).filter(
|
const unqualified_states = Object.keys(delta).filter(
|
||||||
(key) => key.split(".").length === 1
|
(key) => key.split(".").length === 1,
|
||||||
);
|
);
|
||||||
if (unqualified_states.length === 1) {
|
if (unqualified_states.length === 1) {
|
||||||
const main_state = delta[unqualified_states[0]];
|
const main_state = delta[unqualified_states[0]];
|
||||||
@ -690,7 +701,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
|
|||||||
const session_options = client_storage.session_storage[state_key];
|
const session_options = client_storage.session_storage[state_key];
|
||||||
sessionStorage.setItem(
|
sessionStorage.setItem(
|
||||||
session_options.name || state_key,
|
session_options.name || state_key,
|
||||||
delta[substate][key]
|
delta[substate][key],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -710,7 +721,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
|
|||||||
export const useEventLoop = (
|
export const useEventLoop = (
|
||||||
dispatch,
|
dispatch,
|
||||||
initial_events = () => [],
|
initial_events = () => [],
|
||||||
client_storage = {}
|
client_storage = {},
|
||||||
) => {
|
) => {
|
||||||
const socket = useRef(null);
|
const socket = useRef(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -724,7 +735,7 @@ export const useEventLoop = (
|
|||||||
|
|
||||||
event_actions = events.reduce(
|
event_actions = events.reduce(
|
||||||
(acc, e) => ({ ...acc, ...e.event_actions }),
|
(acc, e) => ({ ...acc, ...e.event_actions }),
|
||||||
event_actions ?? {}
|
event_actions ?? {},
|
||||||
);
|
);
|
||||||
|
|
||||||
const _e = args.filter((o) => o?.preventDefault !== undefined)[0];
|
const _e = args.filter((o) => o?.preventDefault !== undefined)[0];
|
||||||
@ -752,7 +763,7 @@ export const useEventLoop = (
|
|||||||
debounce(
|
debounce(
|
||||||
combined_name,
|
combined_name,
|
||||||
() => queueEvents(events, socket),
|
() => queueEvents(events, socket),
|
||||||
event_actions.debounce
|
event_actions.debounce,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
queueEvents(events, socket);
|
queueEvents(events, socket);
|
||||||
@ -771,7 +782,7 @@ export const useEventLoop = (
|
|||||||
query,
|
query,
|
||||||
asPath,
|
asPath,
|
||||||
}))(router),
|
}))(router),
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
sentHydrate.current = true;
|
sentHydrate.current = true;
|
||||||
}
|
}
|
||||||
@ -806,14 +817,10 @@ export const useEventLoop = (
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Main event loop.
|
// Handle socket connect/disconnect.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Skip if the router is not ready.
|
// only use websockets if state is present and backend is not disabled (reflex cloud).
|
||||||
if (!router.isReady) {
|
if (Object.keys(initialState).length > 1 && !isBackendDisabled()) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
// only use websockets if state is present
|
|
||||||
if (Object.keys(initialState).length > 1) {
|
|
||||||
// Initialize the websocket connection.
|
// Initialize the websocket connection.
|
||||||
if (!socket.current) {
|
if (!socket.current) {
|
||||||
connect(
|
connect(
|
||||||
@ -821,16 +828,31 @@ export const useEventLoop = (
|
|||||||
dispatch,
|
dispatch,
|
||||||
["websocket"],
|
["websocket"],
|
||||||
setConnectErrors,
|
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
|
// localStorage event handling
|
||||||
@ -854,7 +876,7 @@ export const useEventLoop = (
|
|||||||
vars[storage_to_state_map[e.key]] = e.newValue;
|
vars[storage_to_state_map[e.key]] = e.newValue;
|
||||||
const event = Event(
|
const event = Event(
|
||||||
`${state_name}.reflex___state____update_vars_internal_state.update_vars_internal`,
|
`${state_name}.reflex___state____update_vars_internal_state.update_vars_internal`,
|
||||||
{ vars: vars }
|
{ vars: vars },
|
||||||
);
|
);
|
||||||
addEvents([event], e);
|
addEvents([event], e);
|
||||||
}
|
}
|
||||||
@ -947,7 +969,7 @@ export const getRefValues = (refs) => {
|
|||||||
return refs.map((ref) =>
|
return refs.map((ref) =>
|
||||||
ref.current
|
ref.current
|
||||||
? ref.current.value || ref.current.getAttribute("aria-valuenow")
|
? 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 __future__ import annotations
|
||||||
|
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from reflex.utils import (
|
from reflex.utils import (
|
||||||
compat, # for side-effects
|
compat, # for side-effects
|
||||||
lazy_loader,
|
lazy_loader,
|
||||||
@ -303,7 +306,6 @@ _MAPPING: dict = {
|
|||||||
"event": [
|
"event": [
|
||||||
"EventChain",
|
"EventChain",
|
||||||
"EventHandler",
|
"EventHandler",
|
||||||
"background",
|
|
||||||
"call_script",
|
"call_script",
|
||||||
"call_function",
|
"call_function",
|
||||||
"run_script",
|
"run_script",
|
||||||
@ -366,20 +368,5 @@ getattr, __dir__, __all__ = lazy_loader.attach(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
def __getattr__(name: ModuleType | Any):
|
||||||
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
|
|
||||||
return getattr(name)
|
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.flex import flex as flex
|
||||||
from .components.radix.themes.layout.grid import grid as grid
|
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_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 ordered_list as ordered_list
|
||||||
from .components.radix.themes.layout.list import unordered_list as unordered_list
|
from .components.radix.themes.layout.list import unordered_list as unordered_list
|
||||||
from .components.radix.themes.layout.section import section as section
|
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 .constants.colors import Color as Color
|
||||||
from .event import EventChain as EventChain
|
from .event import EventChain as EventChain
|
||||||
from .event import EventHandler as EventHandler
|
from .event import EventHandler as EventHandler
|
||||||
from .event import background as background
|
|
||||||
from .event import call_function as call_function
|
from .event import call_function as call_function
|
||||||
from .event import call_script as call_script
|
from .event import call_script as call_script
|
||||||
from .event import clear_local_storage as clear_local_storage
|
from .event import clear_local_storage as clear_local_storage
|
||||||
|
336
reflex/app.py
336
reflex/app.py
@ -27,6 +27,7 @@ from typing import (
|
|||||||
Dict,
|
Dict,
|
||||||
Generic,
|
Generic,
|
||||||
List,
|
List,
|
||||||
|
MutableMapping,
|
||||||
Optional,
|
Optional,
|
||||||
Set,
|
Set,
|
||||||
Type,
|
Type,
|
||||||
@ -53,12 +54,17 @@ from reflex.compiler.compiler import ExecutorSafeFunctions, compile_theme
|
|||||||
from reflex.components.base.app_wrap import AppWrap
|
from reflex.components.base.app_wrap import AppWrap
|
||||||
from reflex.components.base.error_boundary import ErrorBoundary
|
from reflex.components.base.error_boundary import ErrorBoundary
|
||||||
from reflex.components.base.fragment import Fragment
|
from reflex.components.base.fragment import Fragment
|
||||||
|
from reflex.components.base.strict_mode import StrictMode
|
||||||
from reflex.components.component import (
|
from reflex.components.component import (
|
||||||
Component,
|
Component,
|
||||||
ComponentStyle,
|
ComponentStyle,
|
||||||
evaluate_style_namespaces,
|
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.breakpoints import set_breakpoints
|
||||||
from reflex.components.core.client_side_routing import (
|
from reflex.components.core.client_side_routing import (
|
||||||
Default404Page,
|
Default404Page,
|
||||||
@ -145,7 +151,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
|
|||||||
position="top-center",
|
position="top-center",
|
||||||
id="backend_error",
|
id="backend_error",
|
||||||
style={"width": "500px"},
|
style={"width": "500px"},
|
||||||
) # type: ignore
|
) # pyright: ignore [reportReturnType]
|
||||||
else:
|
else:
|
||||||
error_message.insert(0, "An error occurred.")
|
error_message.insert(0, "An error occurred.")
|
||||||
return window_alert("\n".join(error_message))
|
return window_alert("\n".join(error_message))
|
||||||
@ -157,9 +163,12 @@ def default_overlay_component() -> Component:
|
|||||||
Returns:
|
Returns:
|
||||||
The default overlay_component, which is a connection_modal.
|
The default overlay_component, which is a connection_modal.
|
||||||
"""
|
"""
|
||||||
|
config = get_config()
|
||||||
|
|
||||||
return Fragment.create(
|
return Fragment.create(
|
||||||
connection_pulser(),
|
connection_pulser(),
|
||||||
connection_toaster(),
|
connection_toaster(),
|
||||||
|
*([backend_disabled()] if config.is_reflex_cloud else []),
|
||||||
*codespaces.codespaces_auto_redirect(),
|
*codespaces.codespaces_auto_redirect(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -251,36 +260,36 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
# Attributes to add to the html root tag of every page.
|
# Attributes to add to the html root tag of every page.
|
||||||
html_custom_attrs: Optional[Dict[str, str]] = None
|
html_custom_attrs: Optional[Dict[str, str]] = None
|
||||||
|
|
||||||
# A map from a route to an unevaluated page. PRIVATE.
|
# A map from a route to an unevaluated page.
|
||||||
unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
|
_unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
|
||||||
default_factory=dict
|
default_factory=dict
|
||||||
)
|
)
|
||||||
|
|
||||||
# A map from a page route to the component to render. Users should use `add_page`. PRIVATE.
|
# 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)
|
_pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
|
||||||
|
|
||||||
# The backend API object. PRIVATE.
|
# The backend API object.
|
||||||
api: FastAPI = None # type: ignore
|
_api: FastAPI | None = None
|
||||||
|
|
||||||
# The state class to use for the app. PRIVATE.
|
# The state class to use for the app.
|
||||||
state: Optional[Type[BaseState]] = None
|
_state: Optional[Type[BaseState]] = None
|
||||||
|
|
||||||
# Class to manage many client states.
|
# Class to manage many client states.
|
||||||
_state_manager: Optional[StateManager] = None
|
_state_manager: Optional[StateManager] = None
|
||||||
|
|
||||||
# Mapping from a route to event handlers to trigger when the page loads. PRIVATE.
|
# Mapping from a route to event handlers to trigger when the page loads.
|
||||||
load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
|
_load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
|
||||||
default_factory=dict
|
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
|
admin_dash: Optional[AdminDash] = None
|
||||||
|
|
||||||
# The async server name space. PRIVATE.
|
# The async server name space.
|
||||||
event_namespace: Optional[EventNamespace] = None
|
_event_namespace: Optional[EventNamespace] = None
|
||||||
|
|
||||||
# Background tasks that are currently running. PRIVATE.
|
# Background tasks that are currently running.
|
||||||
background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
|
_background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
|
||||||
|
|
||||||
# Frontend Error Handler Function
|
# Frontend Error Handler Function
|
||||||
frontend_exception_handler: Callable[[Exception], None] = (
|
frontend_exception_handler: Callable[[Exception], None] = (
|
||||||
@ -292,6 +301,24 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
[Exception], Union[EventSpec, List[EventSpec], None]
|
[Exception], Union[EventSpec, List[EventSpec], None]
|
||||||
] = default_backend_exception_handler
|
] = 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):
|
def __post_init__(self):
|
||||||
"""Initialize the app.
|
"""Initialize the app.
|
||||||
|
|
||||||
@ -311,7 +338,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
set_breakpoints(self.style.pop("breakpoints"))
|
set_breakpoints(self.style.pop("breakpoints"))
|
||||||
|
|
||||||
# Set up the API.
|
# 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_cors()
|
||||||
self._add_default_endpoints()
|
self._add_default_endpoints()
|
||||||
|
|
||||||
@ -334,8 +361,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
def _enable_state(self) -> None:
|
def _enable_state(self) -> None:
|
||||||
"""Enable state for the app."""
|
"""Enable state for the app."""
|
||||||
if not self.state:
|
if not self._state:
|
||||||
self.state = State
|
self._state = State
|
||||||
self._setup_state()
|
self._setup_state()
|
||||||
|
|
||||||
def _setup_state(self) -> None:
|
def _setup_state(self) -> None:
|
||||||
@ -344,13 +371,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
Raises:
|
Raises:
|
||||||
RuntimeError: If the socket server is invalid.
|
RuntimeError: If the socket server is invalid.
|
||||||
"""
|
"""
|
||||||
if not self.state:
|
if not self._state:
|
||||||
return
|
return
|
||||||
|
|
||||||
config = get_config()
|
config = get_config()
|
||||||
|
|
||||||
# Set up the state manager.
|
# 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.
|
# Set up the Socket.IO AsyncServer.
|
||||||
if not self.sio:
|
if not self.sio:
|
||||||
@ -381,12 +408,42 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
namespace = config.get_event_namespace()
|
namespace = config.get_event_namespace()
|
||||||
|
|
||||||
# Create the event namespace and attach the main app. Not related to any paths.
|
# 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.
|
# Register the event namespace with the socket.
|
||||||
self.sio.register_namespace(self.event_namespace)
|
self.sio.register_namespace(self.event_namespace)
|
||||||
# Mount the socket app with the API.
|
# 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
|
# Check the exception handlers
|
||||||
self._validate_exception_handlers()
|
self._validate_exception_handlers()
|
||||||
@ -397,24 +454,35 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
Returns:
|
Returns:
|
||||||
The string representation of the app.
|
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:
|
def __call__(self) -> FastAPI:
|
||||||
"""Run the backend api instance.
|
"""Run the backend api instance.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the app has not been initialized.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The backend api.
|
The backend api.
|
||||||
"""
|
"""
|
||||||
|
if not self.api:
|
||||||
|
raise ValueError("The app has not been initialized.")
|
||||||
return self.api
|
return self.api
|
||||||
|
|
||||||
def _add_default_endpoints(self):
|
def _add_default_endpoints(self):
|
||||||
"""Add default api endpoints (ping)."""
|
"""Add default api endpoints (ping)."""
|
||||||
# To test the server.
|
# To test the server.
|
||||||
|
if not self.api:
|
||||||
|
return
|
||||||
|
|
||||||
self.api.get(str(constants.Endpoint.PING))(ping)
|
self.api.get(str(constants.Endpoint.PING))(ping)
|
||||||
self.api.get(str(constants.Endpoint.HEALTH))(health)
|
self.api.get(str(constants.Endpoint.HEALTH))(health)
|
||||||
|
|
||||||
def _add_optional_endpoints(self):
|
def _add_optional_endpoints(self):
|
||||||
"""Add optional api endpoints (_upload)."""
|
"""Add optional api endpoints (_upload)."""
|
||||||
|
if not self.api:
|
||||||
|
return
|
||||||
|
|
||||||
if Upload.is_used:
|
if Upload.is_used:
|
||||||
# To upload files.
|
# To upload files.
|
||||||
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
||||||
@ -432,6 +500,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
def _add_cors(self):
|
def _add_cors(self):
|
||||||
"""Add CORS middleware to the app."""
|
"""Add CORS middleware to the app."""
|
||||||
|
if not self.api:
|
||||||
|
return
|
||||||
self.api.add_middleware(
|
self.api.add_middleware(
|
||||||
cors.CORSMiddleware,
|
cors.CORSMiddleware,
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
@ -463,14 +533,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The generated component.
|
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()
|
||||||
return component if isinstance(component, Component) else component()
|
|
||||||
except exceptions.MatchTypeError:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def add_page(
|
def add_page(
|
||||||
self,
|
self,
|
||||||
@ -527,13 +591,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
# Check if the route given is valid
|
# Check if the route given is valid
|
||||||
verify_route_validity(route)
|
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
|
# 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
|
# the latest render function of a route.This applies typically to decorated pages
|
||||||
# since they are only added when app._compile is called.
|
# 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 = (
|
route_name = (
|
||||||
f"`{route}` or `/`"
|
f"`{route}` or `/`"
|
||||||
if route == constants.PageNames.INDEX_ROUTE
|
if route == constants.PageNames.INDEX_ROUTE
|
||||||
@ -546,15 +610,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
# Setup dynamic args for the route.
|
# Setup dynamic args for the route.
|
||||||
# this state assignment is only required for tests using the deprecated state kwarg for App
|
# 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))
|
state.setup_dynamic_args(get_route_args(route))
|
||||||
|
|
||||||
if on_load:
|
if on_load:
|
||||||
self.load_events[route] = (
|
self._load_events[route] = (
|
||||||
on_load if isinstance(on_load, list) else [on_load]
|
on_load if isinstance(on_load, list) else [on_load]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.unevaluated_pages[route] = UnevaluatedPage(
|
self._unevaluated_pages[route] = UnevaluatedPage(
|
||||||
component=component,
|
component=component,
|
||||||
route=route,
|
route=route,
|
||||||
title=title,
|
title=title,
|
||||||
@ -564,14 +628,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
meta=meta,
|
meta=meta,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _compile_page(self, route: str):
|
def _compile_page(self, route: str, save_page: bool = True):
|
||||||
"""Compile a page.
|
"""Compile a page.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
route: The route of the page to compile.
|
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(
|
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:
|
if enable_state:
|
||||||
@ -579,7 +644,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
# Add the page.
|
# Add the page.
|
||||||
self._check_routes_conflict(route)
|
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]]:
|
def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
|
||||||
"""Get the load events for a route.
|
"""Get the load events for a route.
|
||||||
@ -593,7 +659,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
route = route.lstrip("/")
|
route = route.lstrip("/")
|
||||||
if route == "":
|
if route == "":
|
||||||
route = constants.PageNames.INDEX_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):
|
def _check_routes_conflict(self, new_route: str):
|
||||||
"""Verify if there is any conflict between the new route and any existing route.
|
"""Verify if there is any conflict between the new route and any existing route.
|
||||||
@ -617,10 +683,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
|
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
|
||||||
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
|
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
|
||||||
)
|
)
|
||||||
for route in self.pages:
|
for route in self._pages:
|
||||||
replaced_route = replace_brackets_with_keywords(route)
|
replaced_route = replace_brackets_with_keywords(route)
|
||||||
for rw, r, nr in zip(
|
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 rw in segments and r != nr:
|
||||||
# If the slugs in the segments of both routes are not the same, then the route is invalid
|
# If the slugs in the segments of both routes are not the same, then the route is invalid
|
||||||
@ -651,8 +720,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
Args:
|
Args:
|
||||||
component: The component to display at the page.
|
component: The component to display at the page.
|
||||||
title: The title of the page.
|
title: The title of the page.
|
||||||
description: The description of the page.
|
|
||||||
image: The image to display on 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.
|
on_load: The event handler(s) that will be called each time the page load.
|
||||||
meta: The metadata of the page.
|
meta: The metadata of the page.
|
||||||
"""
|
"""
|
||||||
@ -675,6 +744,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
def _setup_admin_dash(self):
|
def _setup_admin_dash(self):
|
||||||
"""Setup the admin dash."""
|
"""Setup the admin dash."""
|
||||||
# Get the admin dash.
|
# Get the admin dash.
|
||||||
|
if not self.api:
|
||||||
|
return
|
||||||
|
|
||||||
admin_dash = self.admin_dash
|
admin_dash = self.admin_dash
|
||||||
|
|
||||||
if admin_dash and admin_dash.models:
|
if admin_dash and admin_dash.models:
|
||||||
@ -716,7 +788,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
frontend_packages = get_config().frontend_packages
|
frontend_packages = get_config().frontend_packages
|
||||||
_frontend_packages = []
|
_frontend_packages = []
|
||||||
for package in 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(
|
console.warn(
|
||||||
f"Tailwind packages are inferred from 'plugins', remove `{package}` from `frontend_packages`"
|
f"Tailwind packages are inferred from 'plugins', remove `{package}` from `frontend_packages`"
|
||||||
)
|
)
|
||||||
@ -779,10 +851,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
def _setup_overlay_component(self):
|
def _setup_overlay_component(self):
|
||||||
"""If a State is not used and no overlay_component is specified, do not render the connection modal."""
|
"""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
|
self.overlay_component = None
|
||||||
for k, component in self.pages.items():
|
for k, component in self._pages.items():
|
||||||
self.pages[k] = self._add_overlay_to_component(component)
|
self._pages[k] = self._add_overlay_to_component(component)
|
||||||
|
|
||||||
def _add_error_boundary_to_component(self, component: Component) -> Component:
|
def _add_error_boundary_to_component(self, component: Component) -> Component:
|
||||||
if self.error_boundary is None:
|
if self.error_boundary is None:
|
||||||
@ -794,14 +866,14 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
def _setup_error_boundary(self):
|
def _setup_error_boundary(self):
|
||||||
"""If a State is not used and no error_boundary is specified, do not render the error boundary."""
|
"""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
|
self.error_boundary = None
|
||||||
|
|
||||||
for k, component in self.pages.items():
|
for k, component in self._pages.items():
|
||||||
# Skip the 404 page
|
# Skip the 404 page
|
||||||
if k == constants.Page404.SLUG:
|
if k == constants.Page404.SLUG:
|
||||||
continue
|
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):
|
def _apply_decorated_pages(self):
|
||||||
"""Add @rx.page decorated pages to the app.
|
"""Add @rx.page decorated pages to the app.
|
||||||
@ -827,21 +899,27 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
Raises:
|
Raises:
|
||||||
VarDependencyError: When a computed var has an invalid dependency.
|
VarDependencyError: When a computed var has an invalid dependency.
|
||||||
"""
|
"""
|
||||||
if not self.state:
|
if not self._state:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not state:
|
if not state:
|
||||||
state = self.state
|
state = self._state
|
||||||
|
|
||||||
for var in state.computed_vars.values():
|
for var in state.computed_vars.values():
|
||||||
if not var._cache:
|
if not var._cache:
|
||||||
continue
|
continue
|
||||||
deps = var._deps(objclass=state)
|
deps = var._deps(objclass=state)
|
||||||
for dep in deps:
|
for state_name, dep_set in deps.items():
|
||||||
if dep not in state.vars and dep not in state.backend_vars:
|
state_cls = (
|
||||||
raise exceptions.VarDependencyError(
|
state.get_root_state().get_class_substate(state_name)
|
||||||
f"ComputedVar {var._js_expr} on state {state.__name__} has an invalid dependency {dep}"
|
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:
|
for substate in state.class_subclasses:
|
||||||
self._validate_var_dependencies(substate)
|
self._validate_var_dependencies(substate)
|
||||||
@ -857,13 +935,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
"""
|
"""
|
||||||
from reflex.utils.exceptions import ReflexRuntimeError
|
from reflex.utils.exceptions import ReflexRuntimeError
|
||||||
|
|
||||||
self.pages = {}
|
self._pages = {}
|
||||||
|
|
||||||
def get_compilation_time() -> str:
|
def get_compilation_time() -> str:
|
||||||
return str(datetime.now().time()).split(".")[0]
|
return str(datetime.now().time()).split(".")[0]
|
||||||
|
|
||||||
# Render a default 404 page if the user didn't supply one
|
# Render a default 404 page if the user didn't supply one
|
||||||
if constants.Page404.SLUG not in self.unevaluated_pages:
|
if constants.Page404.SLUG not in self._unevaluated_pages:
|
||||||
self.add_page(route=constants.Page404.SLUG)
|
self.add_page(route=constants.Page404.SLUG)
|
||||||
|
|
||||||
# Fix up the style.
|
# Fix up the style.
|
||||||
@ -879,20 +957,24 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
# If a theme component was provided, wrap the app with it
|
# If a theme component was provided, wrap the app with it
|
||||||
app_wrappers[(20, "Theme")] = self.theme
|
app_wrappers[(20, "Theme")] = self.theme
|
||||||
|
|
||||||
for route in self.unevaluated_pages:
|
# Get the env mode.
|
||||||
console.debug(f"Evaluating page: {route}")
|
config = get_config()
|
||||||
self._compile_page(route)
|
|
||||||
|
|
||||||
# Add the optional endpoints (_upload)
|
if config.react_strict_mode:
|
||||||
self._add_optional_endpoints()
|
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
|
return
|
||||||
|
|
||||||
self._validate_var_dependencies()
|
|
||||||
self._setup_overlay_component()
|
|
||||||
self._setup_error_boundary()
|
|
||||||
|
|
||||||
# Create a progress bar.
|
# Create a progress bar.
|
||||||
progress = Progress(
|
progress = Progress(
|
||||||
*Progress.get_default_columns()[:-1],
|
*Progress.get_default_columns()[:-1],
|
||||||
@ -901,18 +983,30 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# try to be somewhat accurate - but still not 100%
|
# try to be somewhat accurate - but still not 100%
|
||||||
adhoc_steps_without_executor = 6
|
adhoc_steps_without_executor = 7
|
||||||
fixed_pages_within_executor = 5
|
fixed_pages_within_executor = 5
|
||||||
progress.start()
|
progress.start()
|
||||||
task = progress.add_task(
|
task = progress.add_task(
|
||||||
f"[{get_compilation_time()}] Compiling:",
|
f"[{get_compilation_time()}] Compiling:",
|
||||||
total=len(self.pages)
|
total=len(self._pages)
|
||||||
|
+ (len(self._unevaluated_pages) * 2)
|
||||||
+ fixed_pages_within_executor
|
+ fixed_pages_within_executor
|
||||||
+ adhoc_steps_without_executor,
|
+ adhoc_steps_without_executor,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the env mode.
|
for route in self._unevaluated_pages:
|
||||||
config = get_config()
|
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.
|
# Store the compile results.
|
||||||
compile_results = []
|
compile_results = []
|
||||||
@ -925,7 +1019,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
# This has to happen before compiling stateful components as that
|
# This has to happen before compiling stateful components as that
|
||||||
# prevents recursive functions from reaching all components.
|
# 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.
|
# Add component._get_all_imports() to all_imports.
|
||||||
all_imports.update(component._get_all_imports())
|
all_imports.update(component._get_all_imports())
|
||||||
|
|
||||||
@ -940,12 +1034,12 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
stateful_components_path,
|
stateful_components_path,
|
||||||
stateful_components_code,
|
stateful_components_code,
|
||||||
page_components,
|
page_components,
|
||||||
) = compiler.compile_stateful_components(self.pages.values())
|
) = compiler.compile_stateful_components(self._pages.values())
|
||||||
|
|
||||||
progress.advance(task)
|
progress.advance(task)
|
||||||
|
|
||||||
# Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
|
# 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(
|
raise ReflexRuntimeError(
|
||||||
"To access rx.State in frontend components, at least one "
|
"To access rx.State in frontend components, at least one "
|
||||||
"subclass of rx.State must be defined in the app."
|
"subclass of rx.State must be defined in the app."
|
||||||
@ -959,7 +1053,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
compiler.compile_document_root(
|
compiler.compile_document_root(
|
||||||
self.head_components,
|
self.head_components,
|
||||||
html_lang=self.html_lang,
|
html_lang=self.html_lang,
|
||||||
html_custom_attrs=self.html_custom_attrs, # type: ignore
|
html_custom_attrs=self.html_custom_attrs, # pyright: ignore [reportArgumentType]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -982,20 +1076,20 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
|
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.COMPONENTS[route] = component
|
||||||
|
|
||||||
ExecutorSafeFunctions.STATE = self.state
|
ExecutorSafeFunctions.STATE = self._state
|
||||||
|
|
||||||
with executor:
|
with executor:
|
||||||
result_futures = []
|
result_futures = []
|
||||||
|
|
||||||
def _submit_work(fn, *args, **kwargs):
|
def _submit_work(fn: Callable, *args, **kwargs):
|
||||||
f = executor.submit(fn, *args, **kwargs)
|
f = executor.submit(fn, *args, **kwargs)
|
||||||
result_futures.append(f)
|
result_futures.append(f)
|
||||||
|
|
||||||
# Compile the pre-compiled pages.
|
# Compile the pre-compiled pages.
|
||||||
for route in self.pages:
|
for route in self._pages:
|
||||||
_submit_work(
|
_submit_work(
|
||||||
ExecutorSafeFunctions.compile_page,
|
ExecutorSafeFunctions.compile_page,
|
||||||
route,
|
route,
|
||||||
@ -1030,7 +1124,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
# Compile the contexts.
|
# Compile the contexts.
|
||||||
compile_results.append(
|
compile_results.append(
|
||||||
compiler.compile_contexts(self.state, self.theme),
|
compiler.compile_contexts(self._state, self.theme),
|
||||||
)
|
)
|
||||||
if self.theme is not None:
|
if self.theme is not None:
|
||||||
# Fix #2992 by removing the top-level appearance prop
|
# Fix #2992 by removing the top-level appearance prop
|
||||||
@ -1152,9 +1246,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
task = asyncio.create_task(_coro())
|
task = asyncio.create_task(_coro())
|
||||||
self.background_tasks.add(task)
|
self._background_tasks.add(task)
|
||||||
# Clean up task from background_tasks set when complete.
|
# 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
|
return task
|
||||||
|
|
||||||
def _validate_exception_handlers(self):
|
def _validate_exception_handlers(self):
|
||||||
@ -1164,11 +1258,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
ValueError: If the custom exception handlers are invalid.
|
ValueError: If the custom exception handlers are invalid.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
FRONTEND_ARG_SPEC = {
|
frontend_arg_spec = {
|
||||||
"exception": Exception,
|
"exception": Exception,
|
||||||
}
|
}
|
||||||
|
|
||||||
BACKEND_ARG_SPEC = {
|
backend_arg_spec = {
|
||||||
"exception": Exception,
|
"exception": Exception,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1176,9 +1270,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
["frontend", "backend"],
|
["frontend", "backend"],
|
||||||
[self.frontend_exception_handler, self.backend_exception_handler],
|
[self.frontend_exception_handler, self.backend_exception_handler],
|
||||||
[
|
[
|
||||||
FRONTEND_ARG_SPEC,
|
frontend_arg_spec,
|
||||||
BACKEND_ARG_SPEC,
|
backend_arg_spec,
|
||||||
],
|
],
|
||||||
|
strict=True,
|
||||||
):
|
):
|
||||||
if hasattr(handler_fn, "__name__"):
|
if hasattr(handler_fn, "__name__"):
|
||||||
_fn_name = handler_fn.__name__
|
_fn_name = handler_fn.__name__
|
||||||
@ -1219,7 +1314,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
):
|
):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Provided custom {handler_domain} exception handler `{_fn_name}` has the wrong argument order."
|
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):
|
if not issubclass(arg_annotations[required_arg], Exception):
|
||||||
@ -1320,15 +1415,14 @@ async def process(
|
|||||||
if app._process_background(state, event) is not None:
|
if app._process_background(state, event) is not None:
|
||||||
# `final=True` allows the frontend send more events immediately.
|
# `final=True` allows the frontend send more events immediately.
|
||||||
yield StateUpdate(final=True)
|
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.
|
# Yield the update.
|
||||||
async for update in state._process(event):
|
yield update
|
||||||
# Postprocess the event.
|
|
||||||
update = await app._postprocess(state, event, update)
|
|
||||||
|
|
||||||
# Yield the update.
|
|
||||||
yield update
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
telemetry.send_error(ex, context="backend")
|
telemetry.send_error(ex, context="backend")
|
||||||
|
|
||||||
@ -1523,16 +1617,20 @@ class EventNamespace(AsyncNamespace):
|
|||||||
self.sid_to_token = {}
|
self.sid_to_token = {}
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
def on_connect(self, sid, environ):
|
def on_connect(self, sid: str, environ: dict):
|
||||||
"""Event for when the websocket is connected.
|
"""Event for when the websocket is connected.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sid: The Socket.IO session id.
|
sid: The Socket.IO session id.
|
||||||
environ: The request information, including HTTP headers.
|
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.
|
"""Event for when the websocket disconnects.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -1554,7 +1652,7 @@ class EventNamespace(AsyncNamespace):
|
|||||||
self.emit(str(constants.SocketEvent.EVENT), update, 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.
|
"""Event for receiving front-end websocket events.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
@ -1563,10 +1661,36 @@ class EventNamespace(AsyncNamespace):
|
|||||||
Args:
|
Args:
|
||||||
sid: The Socket.IO session id.
|
sid: The Socket.IO session id.
|
||||||
data: The event data.
|
data: The event data.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
EventDeserializationError: If the event data is not a dictionary.
|
||||||
"""
|
"""
|
||||||
fields = data
|
fields = data
|
||||||
# Get the event.
|
|
||||||
event = Event(**{k: v for k, v in fields.items() if k in _EVENT_FIELDS})
|
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.token_to_sid[event.token] = sid
|
||||||
self.sid_to_token[sid] = event.token
|
self.sid_to_token[sid] = event.token
|
||||||
@ -1595,7 +1719,7 @@ class EventNamespace(AsyncNamespace):
|
|||||||
# Emit the update from processing the event.
|
# Emit the update from processing the event.
|
||||||
await self.emit_update(update=update, sid=sid)
|
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.
|
"""Event for testing the API endpoint.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -12,7 +12,7 @@ from typing import Callable, Coroutine, Set, Union
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
from reflex.utils import console
|
from reflex.utils import console
|
||||||
from reflex.utils.exceptions import InvalidLifespanTaskType
|
from reflex.utils.exceptions import InvalidLifespanTaskTypeError
|
||||||
|
|
||||||
from .mixin import AppMixin
|
from .mixin import AppMixin
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class LifespanMixin(AppMixin):
|
|||||||
try:
|
try:
|
||||||
async with contextlib.AsyncExitStack() as stack:
|
async with contextlib.AsyncExitStack() as stack:
|
||||||
for task in self.lifespan_tasks:
|
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):
|
if isinstance(task, asyncio.Task):
|
||||||
running_tasks.append(task)
|
running_tasks.append(task)
|
||||||
else:
|
else:
|
||||||
@ -61,19 +61,19 @@ class LifespanMixin(AppMixin):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
task: The task to register.
|
task: The task to register.
|
||||||
task_kwargs: The kwargs of the task.
|
**task_kwargs: The kwargs of the task.
|
||||||
|
|
||||||
Raises:
|
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):
|
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."
|
f"Task {task.__name__} of type generator must be decorated with contextlib.asynccontextmanager."
|
||||||
)
|
)
|
||||||
|
|
||||||
if task_kwargs:
|
if task_kwargs:
|
||||||
original_task = task
|
original_task = task
|
||||||
task = functools.partial(task, **task_kwargs) # type: ignore
|
task = functools.partial(task, **task_kwargs) # pyright: ignore [reportArgumentType]
|
||||||
functools.update_wrapper(task, original_task) # type: ignore
|
functools.update_wrapper(task, original_task) # pyright: ignore [reportArgumentType]
|
||||||
self.lifespan_tasks.add(task) # type: ignore
|
self.lifespan_tasks.add(task)
|
||||||
console.debug(f"Registered lifespan task: {task.__name__}") # type: ignore
|
console.debug(f"Registered lifespan task: {task.__name__}") # pyright: ignore [reportAttributeAccessIssue]
|
||||||
|
@ -53,11 +53,11 @@ class MiddlewareMixin(AppMixin):
|
|||||||
"""
|
"""
|
||||||
for middleware in self.middleware:
|
for middleware in self.middleware:
|
||||||
if asyncio.iscoroutinefunction(middleware.preprocess):
|
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:
|
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:
|
if out is not None:
|
||||||
return out # type: ignore
|
return out # pyright: ignore [reportReturnType]
|
||||||
|
|
||||||
async def _postprocess(
|
async def _postprocess(
|
||||||
self, state: BaseState, event: Event, update: StateUpdate
|
self, state: BaseState, event: Event, update: StateUpdate
|
||||||
@ -78,18 +78,18 @@ class MiddlewareMixin(AppMixin):
|
|||||||
for middleware in self.middleware:
|
for middleware in self.middleware:
|
||||||
if asyncio.iscoroutinefunction(middleware.postprocess):
|
if asyncio.iscoroutinefunction(middleware.postprocess):
|
||||||
out = await middleware.postprocess(
|
out = await middleware.postprocess(
|
||||||
app=self, # type: ignore
|
app=self, # pyright: ignore [reportArgumentType]
|
||||||
state=state,
|
state=state,
|
||||||
event=event,
|
event=event,
|
||||||
update=update,
|
update=update,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
out = middleware.postprocess(
|
out = middleware.postprocess(
|
||||||
app=self, # type: ignore
|
app=self, # pyright: ignore [reportArgumentType]
|
||||||
state=state,
|
state=state,
|
||||||
event=event,
|
event=event,
|
||||||
update=update,
|
update=update,
|
||||||
)
|
)
|
||||||
if out is not None:
|
if out is not None:
|
||||||
return out # type: ignore
|
return out # pyright: ignore [reportReturnType]
|
||||||
return update
|
return update
|
||||||
|
@ -5,16 +5,13 @@ Only the app attribute is explicitly exposed.
|
|||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.utils import telemetry
|
|
||||||
from reflex.utils.exec import is_prod_mode
|
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":
|
if constants.CompileVars.APP != "app":
|
||||||
raise AssertionError("unexpected variable name for 'app'")
|
raise AssertionError("unexpected variable name for 'app'")
|
||||||
|
|
||||||
telemetry.send("compile")
|
app, app_module = get_and_validate_app(reload=False)
|
||||||
app_module = get_app(reload=False)
|
|
||||||
app = getattr(app_module, constants.CompileVars.APP)
|
|
||||||
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
|
# 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).
|
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
||||||
app._apply_decorated_pages()
|
app._apply_decorated_pages()
|
||||||
@ -30,8 +27,7 @@ if is_prod_mode():
|
|||||||
# ensure only "app" is exposed.
|
# ensure only "app" is exposed.
|
||||||
del app_module
|
del app_module
|
||||||
del compile_future
|
del compile_future
|
||||||
del get_app
|
del get_and_validate_app
|
||||||
del is_prod_mode
|
del is_prod_mode
|
||||||
del telemetry
|
|
||||||
del constants
|
del constants
|
||||||
del ThreadPoolExecutor
|
del ThreadPoolExecutor
|
||||||
|
@ -13,7 +13,7 @@ except ModuleNotFoundError:
|
|||||||
if not TYPE_CHECKING:
|
if not TYPE_CHECKING:
|
||||||
import pydantic.main as pydantic_main
|
import pydantic.main as pydantic_main
|
||||||
from pydantic import BaseModel
|
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:
|
def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None:
|
||||||
@ -44,13 +44,13 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None
|
|||||||
|
|
||||||
# monkeypatch pydantic validate_field_name method to skip validating
|
# monkeypatch pydantic validate_field_name method to skip validating
|
||||||
# shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True)
|
# 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:
|
if TYPE_CHECKING:
|
||||||
from reflex.vars import Var
|
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.
|
"""The base class subclassed by all Reflex classes.
|
||||||
|
|
||||||
This class wraps Pydantic and provides common methods such as
|
This class wraps Pydantic and provides common methods such as
|
||||||
@ -75,12 +75,12 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
|||||||
"""
|
"""
|
||||||
from reflex.utils.serializers import serialize
|
from reflex.utils.serializers import serialize
|
||||||
|
|
||||||
return self.__config__.json_dumps( # type: ignore
|
return self.__config__.json_dumps(
|
||||||
self.dict(),
|
self.dict(),
|
||||||
default=serialize,
|
default=serialize,
|
||||||
)
|
)
|
||||||
|
|
||||||
def set(self, **kwargs):
|
def set(self, **kwargs: Any):
|
||||||
"""Set multiple fields and return the object.
|
"""Set multiple fields and return the object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -113,12 +113,12 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
|||||||
default_value: The default value of the field
|
default_value: The default value of the field
|
||||||
"""
|
"""
|
||||||
var_name = var._var_field_name
|
var_name = var._var_field_name
|
||||||
new_field = ModelField.infer(
|
new_field = ModelField.infer( # pyright: ignore [reportPossiblyUnboundVariable]
|
||||||
name=var_name,
|
name=var_name,
|
||||||
value=default_value,
|
value=default_value,
|
||||||
annotation=var._var_type,
|
annotation=var._var_type,
|
||||||
class_validators=None,
|
class_validators=None,
|
||||||
config=cls.__config__, # type: ignore
|
config=cls.__config__,
|
||||||
)
|
)
|
||||||
cls.__fields__.update({var_name: new_field})
|
cls.__fields__.update({var_name: new_field})
|
||||||
|
|
||||||
|
@ -239,11 +239,19 @@ def _compile_components(
|
|||||||
component_renders.append(component_render)
|
component_renders.append(component_render)
|
||||||
imports = utils.merge_imports(imports, component_imports)
|
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.
|
# Compile the components page.
|
||||||
return (
|
return (
|
||||||
templates.COMPONENTS.render(
|
templates.COMPONENTS.render(
|
||||||
imports=utils.compile_imports(imports),
|
imports=utils.compile_imports(imports),
|
||||||
components=component_renders,
|
components=component_renders,
|
||||||
|
dynamic_imports=dynamic_imports,
|
||||||
),
|
),
|
||||||
imports,
|
imports,
|
||||||
)
|
)
|
||||||
|
@ -2,17 +2,24 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import concurrent.futures
|
||||||
|
import traceback
|
||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Dict, Optional, Type, Union
|
from typing import Any, Callable, Dict, Optional, Type, Union
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from reflex.utils.exec import is_in_app_harness
|
||||||
from reflex.utils.prerequisites import get_web_dir
|
from reflex.utils.prerequisites import get_web_dir
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pydantic.v1.fields import ModelField
|
from pydantic.v1.fields import ModelField
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
from pydantic.fields import ModelField # type: ignore
|
from pydantic.fields import (
|
||||||
|
ModelField, # pyright: ignore [reportAttributeAccessIssue]
|
||||||
|
)
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.components.base import (
|
from reflex.components.base import (
|
||||||
@ -29,7 +36,7 @@ from reflex.components.base import (
|
|||||||
)
|
)
|
||||||
from reflex.components.component import Component, ComponentStyle, CustomComponent
|
from reflex.components.component import Component, ComponentStyle, CustomComponent
|
||||||
from reflex.istate.storage import Cookie, LocalStorage, SessionStorage
|
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.style import Style
|
||||||
from reflex.utils import console, format, imports, path_ops
|
from reflex.utils import console, format, imports, path_ops
|
||||||
from reflex.utils.imports import ImportVar, ParsedImportDict
|
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)
|
default, rest = compile_import_statement(fields)
|
||||||
|
|
||||||
# prevent lib from being rendered on the page if all imports are non rendered kind
|
# 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
|
continue
|
||||||
|
|
||||||
if not lib:
|
if not lib:
|
||||||
@ -163,13 +170,34 @@ def compile_state(state: Type[BaseState]) -> dict:
|
|||||||
try:
|
try:
|
||||||
initial_state = state(_reflex_internal_init=True).dict(initial=True)
|
initial_state = state(_reflex_internal_init=True).dict(initial=True)
|
||||||
except Exception as e:
|
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(
|
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_state = state(_reflex_internal_init=True).dict(
|
||||||
initial=True, include_computed=False
|
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(
|
def _compile_client_storage_field(
|
||||||
@ -292,6 +320,7 @@ def compile_custom_component(
|
|||||||
"render": render.render(),
|
"render": render.render(),
|
||||||
"hooks": render._get_all_hooks(),
|
"hooks": render._get_all_hooks(),
|
||||||
"custom_code": render._get_all_custom_code(),
|
"custom_code": render._get_all_custom_code(),
|
||||||
|
"dynamic_imports": render._get_all_dynamic_imports(),
|
||||||
},
|
},
|
||||||
imports,
|
imports,
|
||||||
)
|
)
|
||||||
@ -494,7 +523,7 @@ def empty_dir(path: str | Path, keep_files: list[str] | None = None):
|
|||||||
path_ops.rm(element)
|
path_ops.rm(element)
|
||||||
|
|
||||||
|
|
||||||
def is_valid_url(url) -> bool:
|
def is_valid_url(url: str) -> bool:
|
||||||
"""Check if a url is valid.
|
"""Check if a url is valid.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -31,7 +31,7 @@ class Bare(Component):
|
|||||||
return cls(contents=contents)
|
return cls(contents=contents)
|
||||||
else:
|
else:
|
||||||
contents = str(contents) if contents is not None 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, VarData | None]:
|
def _get_all_hooks_internal(self) -> dict[str, VarData | None]:
|
||||||
"""Include the hooks for the component.
|
"""Include the hooks for the component.
|
||||||
|
@ -11,10 +11,11 @@ from reflex.event import EventHandler, set_clipboard
|
|||||||
from reflex.state import FrontendEventExceptionState
|
from reflex.state import FrontendEventExceptionState
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
from reflex.vars.function import ArgsFunctionOperation
|
from reflex.vars.function import ArgsFunctionOperation
|
||||||
|
from reflex.vars.object import ObjectVar
|
||||||
|
|
||||||
|
|
||||||
def on_error_spec(
|
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]]:
|
) -> Tuple[Var[str], Var[str]]:
|
||||||
"""The spec for the on_error event handler.
|
"""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.event import BASE_STATE, EventType
|
||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
from reflex.vars.object import ObjectVar
|
||||||
|
|
||||||
def on_error_spec(
|
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]]: ...
|
) -> Tuple[Var[str], Var[str]]: ...
|
||||||
|
|
||||||
class ErrorBoundary(Component):
|
class ErrorBoundary(Component):
|
||||||
|
@ -53,11 +53,11 @@ class Description(Meta):
|
|||||||
"""A component that displays the title of the current page."""
|
"""A component that displays the title of the current page."""
|
||||||
|
|
||||||
# The type of the description.
|
# The type of the description.
|
||||||
name: str = "description"
|
name: str | None = "description"
|
||||||
|
|
||||||
|
|
||||||
class Image(Meta):
|
class Image(Meta):
|
||||||
"""A component that displays the title of the current page."""
|
"""A component that displays the title of the current page."""
|
||||||
|
|
||||||
# The type of the image.
|
# 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.
|
||||||
|
"""
|
||||||
|
...
|
@ -23,8 +23,6 @@ from typing import (
|
|||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
|
|
||||||
from typing_extensions import deprecated
|
|
||||||
|
|
||||||
import reflex.state
|
import reflex.state
|
||||||
from reflex.base import Base
|
from reflex.base import Base
|
||||||
from reflex.compiler.templates import STATEFUL_COMPONENT
|
from reflex.compiler.templates import STATEFUL_COMPONENT
|
||||||
@ -47,11 +45,10 @@ from reflex.event import (
|
|||||||
EventChain,
|
EventChain,
|
||||||
EventHandler,
|
EventHandler,
|
||||||
EventSpec,
|
EventSpec,
|
||||||
EventVar,
|
|
||||||
no_args_event_spec,
|
no_args_event_spec,
|
||||||
)
|
)
|
||||||
from reflex.style import Style, format_as_emotion
|
from reflex.style import Style, format_as_emotion
|
||||||
from reflex.utils import console, format, imports, types
|
from reflex.utils import format, imports, types
|
||||||
from reflex.utils.imports import (
|
from reflex.utils.imports import (
|
||||||
ImmutableParsedImportDict,
|
ImmutableParsedImportDict,
|
||||||
ImportDict,
|
ImportDict,
|
||||||
@ -153,7 +150,7 @@ class BaseComponent(Base, ABC):
|
|||||||
class ComponentNamespace(SimpleNamespace):
|
class ComponentNamespace(SimpleNamespace):
|
||||||
"""A namespace to manage components with subcomponents."""
|
"""A namespace to manage components with subcomponents."""
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int: # pyright: ignore [reportIncompatibleVariableOverride]
|
||||||
"""Get the hash of the namespace.
|
"""Get the hash of the namespace.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -429,20 +426,22 @@ class Component(BaseComponent, ABC):
|
|||||||
else:
|
else:
|
||||||
continue
|
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.
|
# Check whether the key is a component prop.
|
||||||
if types._issubclass(field_type, Var):
|
if types._issubclass(field_type, Var):
|
||||||
# Used to store the passed types if var type is a union.
|
# Used to store the passed types if var type is a union.
|
||||||
passed_types = None
|
passed_types = None
|
||||||
try:
|
try:
|
||||||
# Try to create a var from the value.
|
kwargs[key] = determine_key(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
|
|
||||||
|
|
||||||
expected_type = fields[key].outer_type_.__args__[0]
|
expected_type = fields[key].outer_type_.__args__[0]
|
||||||
# validate literal fields.
|
# validate literal fields.
|
||||||
@ -463,9 +462,7 @@ class Component(BaseComponent, ABC):
|
|||||||
if types.is_union(passed_type):
|
if types.is_union(passed_type):
|
||||||
# We need to check all possible types in the union.
|
# We need to check all possible types in the union.
|
||||||
passed_types = (
|
passed_types = (
|
||||||
arg
|
arg for arg in passed_type.__args__ if arg is not type(None)
|
||||||
for arg in passed_type.__args__ # type: ignore
|
|
||||||
if arg is not type(None)
|
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
# If the passed var is a union, check if all possible types are valid.
|
# If the passed var is a union, check if all possible types are valid.
|
||||||
@ -492,7 +489,7 @@ class Component(BaseComponent, ABC):
|
|||||||
# Check if the key is an event trigger.
|
# Check if the key is an event trigger.
|
||||||
if key in component_specific_triggers:
|
if key in component_specific_triggers:
|
||||||
kwargs["event_triggers"][key] = EventChain.create(
|
kwargs["event_triggers"][key] = EventChain.create(
|
||||||
value=value, # type: ignore
|
value=value,
|
||||||
args_spec=component_specific_triggers[key],
|
args_spec=component_specific_triggers[key],
|
||||||
key=key,
|
key=key,
|
||||||
)
|
)
|
||||||
@ -545,41 +542,6 @@ class Component(BaseComponent, ABC):
|
|||||||
# Construct the component.
|
# Construct the component.
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@deprecated("Use rx.EventChain.create instead.")
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
console.deprecate(
|
|
||||||
"Component._create_event_chain",
|
|
||||||
"Use rx.EventChain.create instead.",
|
|
||||||
deprecation_version="0.6.8",
|
|
||||||
removal_version="0.7.0",
|
|
||||||
)
|
|
||||||
return EventChain.create(
|
|
||||||
value=value, # type: ignore
|
|
||||||
args_spec=args_spec,
|
|
||||||
key=key,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_event_triggers(
|
def get_event_triggers(
|
||||||
self,
|
self,
|
||||||
) -> Dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]]:
|
) -> Dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]]:
|
||||||
@ -614,7 +576,7 @@ class Component(BaseComponent, ABC):
|
|||||||
annotation = field.annotation
|
annotation = field.annotation
|
||||||
if (metadata := getattr(annotation, "__metadata__", None)) is not None:
|
if (metadata := getattr(annotation, "__metadata__", None)) is not None:
|
||||||
args_spec = metadata[0]
|
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
|
return default_triggers
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
@ -661,8 +623,7 @@ class Component(BaseComponent, ABC):
|
|||||||
if props is None:
|
if props is None:
|
||||||
# Add component props to the tag.
|
# Add component props to the tag.
|
||||||
props = {
|
props = {
|
||||||
attr[:-1] if attr.endswith("_") else attr: getattr(self, attr)
|
attr.removesuffix("_"): getattr(self, attr) for attr in self.get_props()
|
||||||
for attr in self.get_props()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add ref to element if `id` is not None.
|
# Add ref to element if `id` is not None.
|
||||||
@ -740,22 +701,21 @@ class Component(BaseComponent, ABC):
|
|||||||
# Import here to avoid circular imports.
|
# Import here to avoid circular imports.
|
||||||
from reflex.components.base.bare import Bare
|
from reflex.components.base.bare import Bare
|
||||||
from reflex.components.base.fragment import Fragment
|
from reflex.components.base.fragment import Fragment
|
||||||
from reflex.utils.exceptions import ComponentTypeError
|
from reflex.utils.exceptions import ChildrenTypeError
|
||||||
|
|
||||||
# Filter out None props
|
# Filter out None props
|
||||||
props = {key: value for key, value in props.items() if value is not None}
|
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:
|
for child in children:
|
||||||
if isinstance(child, tuple):
|
if isinstance(child, (tuple, list)):
|
||||||
validate_children(child)
|
validate_children(child)
|
||||||
|
|
||||||
# Make sure the child is a valid type.
|
# Make sure the child is a valid type.
|
||||||
if not types._isinstance(child, ComponentChild):
|
if isinstance(child, dict) or not types._isinstance(
|
||||||
raise ComponentTypeError(
|
child, ComponentChild
|
||||||
"Children of Reflex components must be other components, "
|
):
|
||||||
"state vars, or primitive Python types. "
|
raise ChildrenTypeError(component=cls.__name__, child=child)
|
||||||
f"Got child {child} of type {type(child)}.",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate all the children.
|
# Validate all the children.
|
||||||
validate_children(children)
|
validate_children(children)
|
||||||
@ -798,7 +758,7 @@ class Component(BaseComponent, ABC):
|
|||||||
|
|
||||||
# Walk the MRO to call all `add_style` methods.
|
# Walk the MRO to call all `add_style` methods.
|
||||||
for base in self._iter_parent_classes_with_method("add_style"):
|
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:
|
if s is not None:
|
||||||
styles.append(s)
|
styles.append(s)
|
||||||
|
|
||||||
@ -890,7 +850,7 @@ class Component(BaseComponent, ABC):
|
|||||||
else {}
|
else {}
|
||||||
)
|
)
|
||||||
|
|
||||||
def render(self) -> Dict:
|
def render(self) -> dict:
|
||||||
"""Render the component.
|
"""Render the component.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -908,7 +868,7 @@ class Component(BaseComponent, ABC):
|
|||||||
self._replace_prop_names(rendered_dict)
|
self._replace_prop_names(rendered_dict)
|
||||||
return 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.
|
"""Replace the prop names in the render dictionary.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -948,7 +908,7 @@ class Component(BaseComponent, ABC):
|
|||||||
comp.__name__ for comp in (Fragment, Foreach, Cond, Match)
|
comp.__name__ for comp in (Fragment, Foreach, Cond, Match)
|
||||||
]
|
]
|
||||||
|
|
||||||
def validate_child(child):
|
def validate_child(child: Any):
|
||||||
child_name = type(child).__name__
|
child_name = type(child).__name__
|
||||||
|
|
||||||
# Iterate through the immediate children of fragment
|
# Iterate through the immediate children of fragment
|
||||||
@ -1711,7 +1671,7 @@ class CustomComponent(Component):
|
|||||||
if base_value is not None and isinstance(value, Component):
|
if base_value is not None and isinstance(value, Component):
|
||||||
self.component_props[key] = value
|
self.component_props[key] = value
|
||||||
value = base_value._replace(
|
value = base_value._replace(
|
||||||
merge_var_data=VarData( # type: ignore
|
merge_var_data=VarData(
|
||||||
imports=value._get_all_imports(),
|
imports=value._get_all_imports(),
|
||||||
hooks=value._get_all_hooks(),
|
hooks=value._get_all_hooks(),
|
||||||
)
|
)
|
||||||
@ -1744,7 +1704,7 @@ class CustomComponent(Component):
|
|||||||
return hash(self.tag)
|
return hash(self.tag)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_props(cls) -> Set[str]:
|
def get_props(cls) -> Set[str]: # pyright: ignore [reportIncompatibleVariableOverride]
|
||||||
"""Get the props for the component.
|
"""Get the props for the component.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -1839,7 +1799,7 @@ class CustomComponent(Component):
|
|||||||
include_children=include_children, ignore_ids=ignore_ids
|
include_children=include_children, ignore_ids=ignore_ids
|
||||||
)
|
)
|
||||||
|
|
||||||
@lru_cache(maxsize=None) # noqa
|
@lru_cache(maxsize=None) # noqa: B019
|
||||||
def get_component(self) -> Component:
|
def get_component(self) -> Component:
|
||||||
"""Render the component.
|
"""Render the component.
|
||||||
|
|
||||||
@ -1983,7 +1943,7 @@ class StatefulComponent(BaseComponent):
|
|||||||
|
|
||||||
if not should_memoize:
|
if not should_memoize:
|
||||||
# Determine if any Vars have associated data.
|
# 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():
|
if prop_var._get_all_var_data():
|
||||||
should_memoize = True
|
should_memoize = True
|
||||||
break
|
break
|
||||||
@ -2366,8 +2326,8 @@ class MemoizationLeaf(Component):
|
|||||||
"""
|
"""
|
||||||
comp = super().create(*children, **props)
|
comp = super().create(*children, **props)
|
||||||
if comp._get_all_hooks():
|
if comp._get_all_hooks():
|
||||||
comp._memoization_mode = cls._memoization_mode.copy(
|
comp._memoization_mode = dataclasses.replace(
|
||||||
update={"disposition": MemoizationDisposition.ALWAYS}
|
comp._memoization_mode, disposition=MemoizationDisposition.ALWAYS
|
||||||
)
|
)
|
||||||
return comp
|
return comp
|
||||||
|
|
||||||
@ -2428,7 +2388,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
|
|||||||
if tag["name"] == "match":
|
if tag["name"] == "match":
|
||||||
element = tag["cond"]
|
element = tag["cond"]
|
||||||
|
|
||||||
conditionals = tag["default"]
|
conditionals = render_dict_to_var(tag["default"], imported_names)
|
||||||
|
|
||||||
for case in tag["match_cases"][::-1]:
|
for case in tag["match_cases"][::-1]:
|
||||||
condition = case[0].to_string() == element.to_string()
|
condition = case[0].to_string() == element.to_string()
|
||||||
@ -2437,7 +2397,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
|
|||||||
|
|
||||||
conditionals = ternary_operation(
|
conditionals = ternary_operation(
|
||||||
condition,
|
condition,
|
||||||
case[-1],
|
render_dict_to_var(case[-1], imported_names),
|
||||||
conditionals,
|
conditionals,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2496,6 +2456,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
|
|||||||
@dataclasses.dataclass(
|
@dataclasses.dataclass(
|
||||||
eq=False,
|
eq=False,
|
||||||
frozen=True,
|
frozen=True,
|
||||||
|
slots=True,
|
||||||
)
|
)
|
||||||
class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
|
class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
|
||||||
"""A Var that represents a Component."""
|
"""A Var that represents a Component."""
|
||||||
|
@ -4,8 +4,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from reflex import constants
|
||||||
from reflex.components.component import Component
|
from reflex.components.component import Component
|
||||||
from reflex.components.core.cond import cond
|
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.el.elements.typography import Div
|
||||||
from reflex.components.lucide.icon import Icon
|
from reflex.components.lucide.icon import Icon
|
||||||
from reflex.components.radix.themes.components.dialog import (
|
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.number import BooleanVar
|
||||||
from reflex.vars.sequence import LiteralArrayVar
|
from reflex.vars.sequence import LiteralArrayVar
|
||||||
|
|
||||||
connect_error_var_data: VarData = VarData( # type: ignore
|
connect_error_var_data: VarData = VarData(
|
||||||
imports=Imports.EVENTS,
|
imports=Imports.EVENTS,
|
||||||
hooks={Hooks.EVENTS: None},
|
hooks={Hooks.EVENTS: None},
|
||||||
)
|
)
|
||||||
@ -99,14 +101,14 @@ class ConnectionToaster(Toaster):
|
|||||||
"""
|
"""
|
||||||
toast_id = "websocket-error"
|
toast_id = "websocket-error"
|
||||||
target_url = WebsocketTargetURL.create()
|
target_url = WebsocketTargetURL.create()
|
||||||
props = ToastProps( # type: ignore
|
props = ToastProps(
|
||||||
description=LiteralVar.create(
|
description=LiteralVar.create(
|
||||||
f"Check if server is reachable at {target_url}",
|
f"Check if server is reachable at {target_url}",
|
||||||
),
|
),
|
||||||
close_button=True,
|
close_button=True,
|
||||||
duration=120000,
|
duration=120000,
|
||||||
id=toast_id,
|
id=toast_id,
|
||||||
)
|
) # pyright: ignore [reportCallIssue]
|
||||||
|
|
||||||
individual_hooks = [
|
individual_hooks = [
|
||||||
f"const toast_props = {LiteralVar.create(props)!s};",
|
f"const toast_props = {LiteralVar.create(props)!s};",
|
||||||
@ -116,7 +118,7 @@ class ConnectionToaster(Toaster):
|
|||||||
_var_data=VarData(
|
_var_data=VarData(
|
||||||
imports={
|
imports={
|
||||||
"react": ["useEffect", "useState"],
|
"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(
|
).call(
|
||||||
@ -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_banner = ConnectionBanner.create
|
||||||
connection_modal = ConnectionModal.create
|
connection_modal = ConnectionModal.create
|
||||||
connection_toaster = ConnectionToaster.create
|
connection_toaster = ConnectionToaster.create
|
||||||
connection_pulser = ConnectionPulser.create
|
connection_pulser = ConnectionPulser.create
|
||||||
|
backend_disabled = BackendDisabled.create
|
||||||
|
@ -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_banner = ConnectionBanner.create
|
||||||
connection_modal = ConnectionModal.create
|
connection_modal = ConnectionModal.create
|
||||||
connection_toaster = ConnectionToaster.create
|
connection_toaster = ConnectionToaster.create
|
||||||
connection_pulser = ConnectionPulser.create
|
connection_pulser = ConnectionPulser.create
|
||||||
|
backend_disabled = BackendDisabled.create
|
||||||
|
@ -82,7 +82,9 @@ class Breakpoints(Dict[K, V]):
|
|||||||
return Breakpoints(
|
return Breakpoints(
|
||||||
{
|
{
|
||||||
k: v
|
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
|
if v is not None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -41,7 +41,7 @@ class ClientSideRouting(Component):
|
|||||||
return ""
|
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.
|
"""Wait for a redirect to occur before rendering a component.
|
||||||
|
|
||||||
This prevents the 404 page from flashing while the redirect is happening.
|
This prevents the 404 page from flashing while the redirect is happening.
|
||||||
|
@ -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):
|
class Default404Page(Component):
|
||||||
@overload
|
@overload
|
||||||
|
@ -26,10 +26,9 @@ class Cond(MemoizationLeaf):
|
|||||||
cond: Var[Any]
|
cond: Var[Any]
|
||||||
|
|
||||||
# The component to render if the cond is true.
|
# 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.
|
# The component to render if the cond is false.
|
||||||
comp2: BaseComponent = None # type: ignore
|
comp2: BaseComponent | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
@ -73,8 +72,8 @@ class Cond(MemoizationLeaf):
|
|||||||
def _render(self) -> Tag:
|
def _render(self) -> Tag:
|
||||||
return CondTag(
|
return CondTag(
|
||||||
cond=self.cond,
|
cond=self.cond,
|
||||||
true_value=self.comp1.render(),
|
true_value=self.comp1.render(), # pyright: ignore [reportOptionalMemberAccess]
|
||||||
false_value=self.comp2.render(),
|
false_value=self.comp2.render(), # pyright: ignore [reportOptionalMemberAccess]
|
||||||
)
|
)
|
||||||
|
|
||||||
def render(self) -> Dict:
|
def render(self) -> Dict:
|
||||||
@ -111,7 +110,7 @@ class Cond(MemoizationLeaf):
|
|||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def cond(condition: Any, c1: Component, c2: Any) -> Component: ...
|
def cond(condition: Any, c1: Component, c2: Any) -> Component: ... # pyright: ignore [reportOverlappingOverload]
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@ -154,7 +153,7 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
|
|||||||
if c2 is None:
|
if c2 is None:
|
||||||
raise ValueError("For conditional vars, the second argument must be set.")
|
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)
|
return LiteralVar.create(cond_part)
|
||||||
|
|
||||||
# convert the truth and false cond parts into vars so the _var_data can be obtained.
|
# 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.
|
# Create the conditional var.
|
||||||
return ternary_operation(
|
return ternary_operation(
|
||||||
cond_var.bool()._replace( # type: ignore
|
cond_var.bool()._replace(
|
||||||
merge_var_data=VarData(imports=_IS_TRUE_IMPORT),
|
merge_var_data=VarData(imports=_IS_TRUE_IMPORT),
|
||||||
), # type: ignore
|
),
|
||||||
c1,
|
c1,
|
||||||
c2,
|
c2,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@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
|
@overload
|
||||||
|
@ -28,7 +28,7 @@ class DebounceInput(Component):
|
|||||||
min_length: Var[int]
|
min_length: Var[int]
|
||||||
|
|
||||||
# Time to wait between end of input and triggering on_change
|
# 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
|
# If true, notify when Enter key is pressed
|
||||||
force_notify_by_enter: Var[bool]
|
force_notify_by_enter: Var[bool]
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
from typing import Any, Callable, Iterable
|
from typing import Any, Callable, Iterable
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ from reflex.components.component import Component
|
|||||||
from reflex.components.tags import IterTag
|
from reflex.components.tags import IterTag
|
||||||
from reflex.constants import MemoizationMode
|
from reflex.constants import MemoizationMode
|
||||||
from reflex.state import ComponentState
|
from reflex.state import ComponentState
|
||||||
|
from reflex.utils.exceptions import UntypedVarError
|
||||||
from reflex.vars.base import LiteralVar, Var
|
from reflex.vars.base import LiteralVar, Var
|
||||||
|
|
||||||
|
|
||||||
@ -50,6 +52,7 @@ class Foreach(Component):
|
|||||||
Raises:
|
Raises:
|
||||||
ForeachVarError: If the iterable is of type Any.
|
ForeachVarError: If the iterable is of type Any.
|
||||||
TypeError: If the render function is a ComponentState.
|
TypeError: If the render function is a ComponentState.
|
||||||
|
UntypedVarError: If the iterable is of type Any without a type annotation.
|
||||||
"""
|
"""
|
||||||
iterable = LiteralVar.create(iterable)
|
iterable = LiteralVar.create(iterable)
|
||||||
if iterable._var_type == Any:
|
if iterable._var_type == Any:
|
||||||
@ -71,8 +74,14 @@ class Foreach(Component):
|
|||||||
iterable=iterable,
|
iterable=iterable,
|
||||||
render_fn=render_fn,
|
render_fn=render_fn,
|
||||||
)
|
)
|
||||||
# Keep a ref to a rendered component to determine correct imports/hooks/styles.
|
try:
|
||||||
component.children = [component._render().render_component()]
|
# 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
|
return component
|
||||||
|
|
||||||
def _render(self) -> IterTag:
|
def _render(self) -> IterTag:
|
||||||
@ -97,9 +106,20 @@ class Foreach(Component):
|
|||||||
# Determine the index var name based on the params accepted by render_fn.
|
# Determine the index var name based on the params accepted by render_fn.
|
||||||
props["index_var_name"] = params[1].name
|
props["index_var_name"] = params[1].name
|
||||||
else:
|
else:
|
||||||
|
render_fn = self.render_fn
|
||||||
# Otherwise, use a deterministic index, based on the render function bytecode.
|
# Otherwise, use a deterministic index, based on the render function bytecode.
|
||||||
code_hash = (
|
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(
|
.to_bytes(
|
||||||
length=8,
|
length=8,
|
||||||
byteorder="big",
|
byteorder="big",
|
||||||
|
@ -14,7 +14,7 @@ class Html(Div):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# The HTML to render.
|
# The HTML to render.
|
||||||
dangerouslySetInnerHTML: Var[Dict[str, str]]
|
dangerouslySetInnerHTML: Var[Dict[str, str]] # noqa: N815
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, *children, **props):
|
def create(cls, *children, **props):
|
||||||
|
@ -109,7 +109,7 @@ class Match(MemoizationLeaf):
|
|||||||
return cases, default
|
return cases, default
|
||||||
|
|
||||||
@classmethod
|
@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
|
"""Convert a case element into a Var.If the case
|
||||||
is a Style type, we extract the var data and merge it with the
|
is a Style type, we extract the var data and merge it with the
|
||||||
newly created Var.
|
newly created Var.
|
||||||
@ -222,7 +222,7 @@ class Match(MemoizationLeaf):
|
|||||||
cond=match_cond_var,
|
cond=match_cond_var,
|
||||||
match_cases=match_cases,
|
match_cases=match_cases,
|
||||||
default=default,
|
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(
|
_js_expr=format.format_match(
|
||||||
cond=str(match_cond_var),
|
cond=str(match_cond_var),
|
||||||
match_cases=match_cases,
|
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(
|
_var_data=VarData.merge(
|
||||||
match_cond_var._get_all_var_data(),
|
match_cond_var._get_all_var_data(),
|
||||||
*[el._get_all_var_data() for case in match_cases for el in case],
|
*[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]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ class GhostUpload(Fragment):
|
|||||||
class Upload(MemoizationLeaf):
|
class Upload(MemoizationLeaf):
|
||||||
"""A file upload component."""
|
"""A file upload component."""
|
||||||
|
|
||||||
library = "react-dropzone@14.2.10"
|
library = "react-dropzone@14.3.5"
|
||||||
|
|
||||||
tag = ""
|
tag = ""
|
||||||
|
|
||||||
@ -269,7 +269,7 @@ class Upload(MemoizationLeaf):
|
|||||||
on_drop = upload_props["on_drop"]
|
on_drop = upload_props["on_drop"]
|
||||||
if isinstance(on_drop, Callable):
|
if isinstance(on_drop, Callable):
|
||||||
# Call the lambda to get the event chain.
|
# 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):
|
if isinstance(on_drop, EventSpec):
|
||||||
# Update the provided args for direct use with on_drop.
|
# Update the provided args for direct use with on_drop.
|
||||||
on_drop = on_drop.with_args(
|
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.constants.colors import Color
|
||||||
from reflex.event import set_clipboard
|
from reflex.event import set_clipboard
|
||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
from reflex.utils import console, format
|
from reflex.utils import format
|
||||||
from reflex.utils.imports import ImportVar
|
from reflex.utils.imports import ImportVar
|
||||||
from reflex.vars.base import LiteralVar, Var, VarData
|
from reflex.vars.base import LiteralVar, Var, VarData
|
||||||
|
|
||||||
@ -382,7 +382,7 @@ for theme_name in dir(Theme):
|
|||||||
class CodeBlock(Component, MarkdownComponentMap):
|
class CodeBlock(Component, MarkdownComponentMap):
|
||||||
"""A code block."""
|
"""A code block."""
|
||||||
|
|
||||||
library = "react-syntax-highlighter@15.6.0"
|
library = "react-syntax-highlighter@15.6.1"
|
||||||
|
|
||||||
tag = "PrismAsyncLight"
|
tag = "PrismAsyncLight"
|
||||||
|
|
||||||
@ -438,6 +438,8 @@ class CodeBlock(Component, MarkdownComponentMap):
|
|||||||
can_copy = props.pop("can_copy", False)
|
can_copy = props.pop("can_copy", False)
|
||||||
copy_button = props.pop("copy_button", None)
|
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:
|
if "theme" not in props:
|
||||||
# Default color scheme responds to global color mode.
|
# Default color scheme responds to global color mode.
|
||||||
props["theme"] = color_mode_cond(
|
props["theme"] = color_mode_cond(
|
||||||
@ -445,20 +447,9 @@ class CodeBlock(Component, MarkdownComponentMap):
|
|||||||
dark=Theme.one_dark,
|
dark=Theme.one_dark,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 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" 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:
|
if can_copy:
|
||||||
code = children[0]
|
code = children[0]
|
||||||
copy_button = ( # type: ignore
|
copy_button = (
|
||||||
copy_button
|
copy_button
|
||||||
if copy_button is not None
|
if copy_button is not None
|
||||||
else Button.create(
|
else Button.create(
|
||||||
|
@ -165,7 +165,7 @@ class DataEditor(NoSSRComponent):
|
|||||||
|
|
||||||
tag = "DataEditor"
|
tag = "DataEditor"
|
||||||
is_default = True
|
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] = [
|
lib_dependencies: List[str] = [
|
||||||
"lodash@^4.17.21",
|
"lodash@^4.17.21",
|
||||||
"react-responsive-carousel@^3.2.7",
|
"react-responsive-carousel@^3.2.7",
|
||||||
@ -321,6 +321,8 @@ class DataEditor(NoSSRComponent):
|
|||||||
Returns:
|
Returns:
|
||||||
The import dict.
|
The import dict.
|
||||||
"""
|
"""
|
||||||
|
if self.library is None:
|
||||||
|
return {}
|
||||||
return {
|
return {
|
||||||
"": f"{format.format_library_name(self.library)}/dist/index.css",
|
"": f"{format.format_library_name(self.library)}/dist/index.css",
|
||||||
self.library: "GridCellKind",
|
self.library: "GridCellKind",
|
||||||
@ -343,9 +345,9 @@ class DataEditor(NoSSRComponent):
|
|||||||
data_callback = self.get_cell_content._js_expr
|
data_callback = self.get_cell_content._js_expr
|
||||||
else:
|
else:
|
||||||
data_callback = f"getData_{editor_id}"
|
data_callback = f"getData_{editor_id}"
|
||||||
self.get_cell_content = Var(_js_expr=data_callback) # type: ignore
|
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)
|
columns_path = str(self.columns)
|
||||||
data_path = str(self.data)
|
data_path = str(self.data)
|
||||||
@ -385,7 +387,8 @@ class DataEditor(NoSSRComponent):
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
"DataEditor data must be an ArrayVar if rows is not provided."
|
"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 not isinstance(columns, Var) and len(columns):
|
||||||
if types.is_dataframe(type(data)) or (
|
if types.is_dataframe(type(data)) or (
|
||||||
|
@ -15,10 +15,8 @@ def svg_logo(color: Union[str, rx.Var[str]] = rx.color_mode_cond("#110F1F", "whi
|
|||||||
The Reflex logo SVG.
|
The Reflex logo SVG.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def logo_path(d):
|
def logo_path(d: str):
|
||||||
return rx.el.svg.path(
|
return rx.el.svg.path(d=d)
|
||||||
d=d,
|
|
||||||
)
|
|
||||||
|
|
||||||
paths = [
|
paths = [
|
||||||
"M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z",
|
"M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z",
|
||||||
|
@ -602,7 +602,7 @@ class ShikiCodeBlock(Component, MarkdownComponentMap):
|
|||||||
|
|
||||||
transformer_styles = {}
|
transformer_styles = {}
|
||||||
# Collect styles from transformers and wrapper
|
# 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:
|
if isinstance(transformer, ShikiBaseTransformers) and transformer.style:
|
||||||
transformer_styles.update(transformer.style)
|
transformer_styles.update(transformer.style)
|
||||||
transformer_styles.update(code_wrapper_props.pop("style", {}))
|
transformer_styles.update(code_wrapper_props.pop("style", {}))
|
||||||
@ -621,18 +621,22 @@ class ShikiCodeBlock(Component, MarkdownComponentMap):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Imports for the component.
|
Imports for the component.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the transformers are not of type LiteralVar.
|
||||||
"""
|
"""
|
||||||
imports = defaultdict(list)
|
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:
|
for transformer in self.transformers._var_value:
|
||||||
if isinstance(transformer, ShikiBaseTransformers):
|
if isinstance(transformer, ShikiBaseTransformers):
|
||||||
imports[transformer.library].extend(
|
imports[transformer.library].extend(
|
||||||
[ImportVar(tag=str(fn)) for fn in transformer.fns]
|
[ImportVar(tag=str(fn)) for fn in transformer.fns]
|
||||||
)
|
)
|
||||||
(
|
if transformer.library not in self.lib_dependencies:
|
||||||
self.lib_dependencies.append(transformer.library)
|
self.lib_dependencies.append(transformer.library)
|
||||||
if transformer.library not in self.lib_dependencies
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
return imports
|
return imports
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -653,8 +657,9 @@ class ShikiCodeBlock(Component, MarkdownComponentMap):
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"the function names should be str names of functions in the specified transformer: {library!r}"
|
f"the function names should be str names of functions in the specified transformer: {library!r}"
|
||||||
)
|
)
|
||||||
return ShikiBaseTransformers( # type: ignore
|
return ShikiBaseTransformers(
|
||||||
library=library, fns=[FunctionStringVar.create(fn) for fn in fns]
|
library=library,
|
||||||
|
fns=[FunctionStringVar.create(fn) for fn in fns], # pyright: ignore [reportCallIssue]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _render(self, props: dict[str, Any] | None = None):
|
def _render(self, props: dict[str, Any] | None = None):
|
||||||
@ -757,13 +762,13 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
|
|||||||
|
|
||||||
if can_copy:
|
if can_copy:
|
||||||
code = children[0]
|
code = children[0]
|
||||||
copy_button = ( # type: ignore
|
copy_button = (
|
||||||
copy_button
|
copy_button
|
||||||
if copy_button is not None
|
if copy_button is not None
|
||||||
else Button.create(
|
else Button.create(
|
||||||
Icon.create(tag="copy", size=16, color=color("gray", 11)),
|
Icon.create(tag="copy", size=16, color=color("gray", 11)),
|
||||||
on_click=[
|
on_click=[
|
||||||
set_clipboard(cls._strip_transformer_triggers(code)), # type: ignore
|
set_clipboard(cls._strip_transformer_triggers(code)),
|
||||||
copy_script(),
|
copy_script(),
|
||||||
],
|
],
|
||||||
style=Style(
|
style=Style(
|
||||||
|
@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Union
|
|||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.utils import imports
|
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.format import format_library_name
|
||||||
from reflex.utils.serializers import serializer
|
from reflex.utils.serializers import serializer
|
||||||
from reflex.vars import Var, get_unique_variable_name
|
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.
|
component: The component to bundle the library with.
|
||||||
|
|
||||||
Raises:
|
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):
|
if isinstance(component, str):
|
||||||
bundled_libraries.add(component)
|
bundled_libraries.add(component)
|
||||||
return
|
return
|
||||||
if component.library is None:
|
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))
|
bundled_libraries.add(format_library_name(component.library))
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,4 +48,4 @@ PROP_TO_ELEMENTS = {
|
|||||||
ELEMENT_TO_PROPS = defaultdict(list)
|
ELEMENT_TO_PROPS = defaultdict(list)
|
||||||
for prop, elements in PROP_TO_ELEMENTS.items():
|
for prop, elements in PROP_TO_ELEMENTS.items():
|
||||||
for el in elements:
|
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):
|
class Element(Component):
|
||||||
"""The base class for all raw HTML elements."""
|
"""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.
|
"""Two elements are equal if they have the same tag.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -102,7 +102,7 @@ class Fieldset(Element):
|
|||||||
name: Var[Union[str, int, bool]]
|
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.
|
"""Event handler spec for the on_submit event.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -111,7 +111,7 @@ def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
|
|||||||
return (FORM_DATA,)
|
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.
|
"""Event handler spec for the on_submit event.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -153,7 +153,7 @@ class Form(BaseHTML):
|
|||||||
target: Var[Union[str, int, bool]]
|
target: Var[Union[str, int, bool]]
|
||||||
|
|
||||||
# If true, the form will be cleared after submit.
|
# 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.
|
# The name used to make this form's submit handler function unique.
|
||||||
handle_submit_unique_name: Var[str]
|
handle_submit_unique_name: Var[str]
|
||||||
@ -405,7 +405,7 @@ class Input(BaseHTML):
|
|||||||
(value_var := Var.create(value))._var_type
|
(value_var := Var.create(value))._var_type
|
||||||
):
|
):
|
||||||
props["value"] = ternary_operation(
|
props["value"] = ternary_operation(
|
||||||
(value_var != Var.create(None)) # pyright: ignore [reportGeneralTypeIssues]
|
(value_var != Var.create(None)) # pyright: ignore [reportArgumentType]
|
||||||
& (value_var != Var(_js_expr="undefined")),
|
& (value_var != Var(_js_expr="undefined")),
|
||||||
value,
|
value,
|
||||||
Var.create(""),
|
Var.create(""),
|
||||||
|
@ -270,8 +270,8 @@ class Fieldset(Element):
|
|||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]: ...
|
def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]: ...
|
||||||
def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]: ...
|
def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]: ...
|
||||||
|
|
||||||
class Form(BaseHTML):
|
class Form(BaseHTML):
|
||||||
@overload
|
@overload
|
||||||
@ -341,10 +341,10 @@ class Form(BaseHTML):
|
|||||||
on_submit: Optional[
|
on_submit: Optional[
|
||||||
Union[
|
Union[
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||||
],
|
],
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
|
@ -8,7 +8,7 @@ from functools import lru_cache
|
|||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from typing import Any, Callable, Dict, Sequence, Union
|
from typing import Any, Callable, Dict, Sequence, Union
|
||||||
|
|
||||||
from reflex.components.component import Component, CustomComponent
|
from reflex.components.component import BaseComponent, Component, CustomComponent
|
||||||
from reflex.components.tags.tag import Tag
|
from reflex.components.tags.tag import Tag
|
||||||
from reflex.utils import types
|
from reflex.utils import types
|
||||||
from reflex.utils.imports import ImportDict, ImportVar
|
from reflex.utils.imports import ImportDict, ImportVar
|
||||||
@ -65,8 +65,8 @@ def get_base_component_map() -> dict[str, Callable]:
|
|||||||
"h5": lambda value: Heading.create(value, as_="h5", size="2", margin_y="0.5em"),
|
"h5": lambda value: Heading.create(value, as_="h5", size="2", margin_y="0.5em"),
|
||||||
"h6": lambda value: Heading.create(value, as_="h6", size="1", margin_y="0.5em"),
|
"h6": lambda value: Heading.create(value, as_="h6", size="1", margin_y="0.5em"),
|
||||||
"p": lambda value: Text.create(value, margin_y="1em"),
|
"p": lambda value: Text.create(value, margin_y="1em"),
|
||||||
"ul": lambda value: UnorderedList.create(value, margin_y="1em"), # type: ignore
|
"ul": lambda value: UnorderedList.create(value, margin_y="1em"),
|
||||||
"ol": lambda value: OrderedList.create(value, margin_y="1em"), # type: ignore
|
"ol": lambda value: OrderedList.create(value, margin_y="1em"),
|
||||||
"li": lambda value: ListItem.create(value, margin_y="0.5em"),
|
"li": lambda value: ListItem.create(value, margin_y="0.5em"),
|
||||||
"a": lambda value: Link.create(value),
|
"a": lambda value: Link.create(value),
|
||||||
"code": lambda value: Code.create(value),
|
"code": lambda value: Code.create(value),
|
||||||
@ -236,7 +236,7 @@ class Markdown(Component):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
*[
|
*[
|
||||||
component(_MOCK_ARG)._get_all_imports() # type: ignore
|
component(_MOCK_ARG)._get_all_imports()
|
||||||
for component in self.component_map.values()
|
for component in self.component_map.values()
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
@ -327,7 +327,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
|||||||
if tag != "codeblock"
|
if tag != "codeblock"
|
||||||
# For codeblock, the mapping for some cases returns an array of elements. Let's join them into a string.
|
# For codeblock, the mapping for some cases returns an array of elements. Let's join them into a string.
|
||||||
else ternary_operation(
|
else ternary_operation(
|
||||||
ARRAY_ISARRAY.call(_CHILDREN), # type: ignore
|
ARRAY_ISARRAY.call(_CHILDREN), # pyright: ignore [reportArgumentType]
|
||||||
_CHILDREN.to(list).join("\n"),
|
_CHILDREN.to(list).join("\n"),
|
||||||
_CHILDREN,
|
_CHILDREN,
|
||||||
).to(str)
|
).to(str)
|
||||||
@ -379,7 +379,9 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
|||||||
# fallback to the default fn Var creation if the component is not a MarkdownComponentMap.
|
# fallback to the default fn Var creation if the component is not a MarkdownComponentMap.
|
||||||
return MarkdownComponentMap.create_map_fn_var(fn_body=formatted_component)
|
return MarkdownComponentMap.create_map_fn_var(fn_body=formatted_component)
|
||||||
|
|
||||||
def _get_map_fn_custom_code_from_children(self, component) -> list[str]:
|
def _get_map_fn_custom_code_from_children(
|
||||||
|
self, component: BaseComponent
|
||||||
|
) -> list[str]:
|
||||||
"""Recursively get markdown custom code from children components.
|
"""Recursively get markdown custom code from children components.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -409,7 +411,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
|||||||
return custom_code_list
|
return custom_code_list
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _component_map_hash(component_map) -> str:
|
def _component_map_hash(component_map: dict) -> str:
|
||||||
inp = str(
|
inp = str(
|
||||||
{tag: component(_MOCK_ARG) for tag, component in component_map.items()}
|
{tag: component(_MOCK_ARG) for tag, component in component_map.items()}
|
||||||
).encode()
|
).encode()
|
||||||
@ -425,7 +427,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
|||||||
for _component in self.component_map.values():
|
for _component in self.component_map.values():
|
||||||
comp = _component(_MOCK_ARG)
|
comp = _component(_MOCK_ARG)
|
||||||
hooks.update(comp._get_all_hooks())
|
hooks.update(comp._get_all_hooks())
|
||||||
formatted_hooks = MACROS.module.renderHooks(hooks) # type: ignore
|
formatted_hooks = MACROS.module.renderHooks(hooks) # pyright: ignore [reportAttributeAccessIssue]
|
||||||
return f"""
|
return f"""
|
||||||
function {self._get_component_map_name()} () {{
|
function {self._get_component_map_name()} () {{
|
||||||
{formatted_hooks}
|
{formatted_hooks}
|
||||||
|
@ -28,9 +28,9 @@ class MomentDelta:
|
|||||||
class Moment(NoSSRComponent):
|
class Moment(NoSSRComponent):
|
||||||
"""The Moment component."""
|
"""The Moment component."""
|
||||||
|
|
||||||
tag: str = "Moment"
|
tag: str | None = "Moment"
|
||||||
is_default = True
|
is_default = True
|
||||||
library: str = "react-moment"
|
library: str | None = "react-moment"
|
||||||
lib_dependencies: List[str] = ["moment"]
|
lib_dependencies: List[str] = ["moment"]
|
||||||
|
|
||||||
# How often the date update (how often time update / 0 to disable).
|
# How often the date update (how often time update / 0 to disable).
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
"""Image component from next/image."""
|
"""Image component from next/image."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Literal, Optional, Union
|
from typing import Any, Literal, Optional, Union
|
||||||
|
|
||||||
from reflex.event import EventHandler, no_args_event_spec
|
from reflex.event import EventHandler, no_args_event_spec
|
||||||
from reflex.utils import types
|
from reflex.utils import console, types
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
from .base import NextComponent
|
from .base import NextComponent
|
||||||
|
|
||||||
|
DEFAULT_W_H = "100%"
|
||||||
|
|
||||||
|
|
||||||
class Image(NextComponent):
|
class Image(NextComponent):
|
||||||
"""Display an image."""
|
"""Display an image."""
|
||||||
@ -53,7 +57,7 @@ class Image(NextComponent):
|
|||||||
loading: Var[Literal["lazy", "eager"]]
|
loading: Var[Literal["lazy", "eager"]]
|
||||||
|
|
||||||
# A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
|
# A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
|
||||||
blurDataURL: Var[str]
|
blur_data_url: Var[str]
|
||||||
|
|
||||||
# Fires when the image has loaded.
|
# Fires when the image has loaded.
|
||||||
on_load: EventHandler[no_args_event_spec]
|
on_load: EventHandler[no_args_event_spec]
|
||||||
@ -80,10 +84,18 @@ class Image(NextComponent):
|
|||||||
Returns:
|
Returns:
|
||||||
_type_: _description_
|
_type_: _description_
|
||||||
"""
|
"""
|
||||||
style = props.get("style", {})
|
if "blurDataURL" in props:
|
||||||
DEFAULT_W_H = "100%"
|
console.deprecate(
|
||||||
|
feature_name="blurDataURL",
|
||||||
|
reason="Use blur_data_url instead",
|
||||||
|
deprecation_version="0.7.0",
|
||||||
|
removal_version="0.8.0",
|
||||||
|
)
|
||||||
|
props["blur_data_url"] = props.pop("blurDataURL")
|
||||||
|
|
||||||
def check_prop_type(prop_name, prop_value):
|
style = props.get("style", {})
|
||||||
|
|
||||||
|
def check_prop_type(prop_name: str, prop_value: int | str | None):
|
||||||
if types.check_prop_in_allowed_types(prop_value, allowed_types=[int]):
|
if types.check_prop_in_allowed_types(prop_value, allowed_types=[int]):
|
||||||
props[prop_name] = prop_value
|
props[prop_name] = prop_value
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ from reflex.vars.base import Var
|
|||||||
|
|
||||||
from .base import NextComponent
|
from .base import NextComponent
|
||||||
|
|
||||||
|
DEFAULT_W_H = "100%"
|
||||||
|
|
||||||
class Image(NextComponent):
|
class Image(NextComponent):
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -30,7 +32,7 @@ class Image(NextComponent):
|
|||||||
loading: Optional[
|
loading: Optional[
|
||||||
Union[Literal["eager", "lazy"], Var[Literal["eager", "lazy"]]]
|
Union[Literal["eager", "lazy"], Var[Literal["eager", "lazy"]]]
|
||||||
] = None,
|
] = None,
|
||||||
blurDataURL: Optional[Union[Var[str], str]] = None,
|
blur_data_url: Optional[Union[Var[str], str]] = None,
|
||||||
style: Optional[Style] = None,
|
style: Optional[Style] = None,
|
||||||
key: Optional[Any] = None,
|
key: Optional[Any] = None,
|
||||||
id: Optional[Any] = None,
|
id: Optional[Any] = None,
|
||||||
@ -71,7 +73,7 @@ class Image(NextComponent):
|
|||||||
priority: When true, the image will be considered high priority and preload. Lazy loading is automatically disabled for images using priority.
|
priority: When true, the image will be considered high priority and preload. Lazy loading is automatically disabled for images using priority.
|
||||||
placeholder: A placeholder to use while the image is loading. Possible values are blur, empty, or data:image/.... Defaults to empty.
|
placeholder: A placeholder to use while the image is loading. Possible values are blur, empty, or data:image/.... Defaults to empty.
|
||||||
loading: The loading behavior of the image. Defaults to lazy. Can hurt performance, recommended to use `priority` instead.
|
loading: The loading behavior of the image. Defaults to lazy. Can hurt performance, recommended to use `priority` instead.
|
||||||
blurDataURL: A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
|
blur_data_url: A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
|
||||||
on_load: Fires when the image has loaded.
|
on_load: Fires when the image has loaded.
|
||||||
on_error: Fires when the image has an error.
|
on_error: Fires when the image has an error.
|
||||||
style: The style of the component.
|
style: The style of the component.
|
||||||
|
@ -17,4 +17,4 @@ class NextLink(Component):
|
|||||||
href: Var[str]
|
href: Var[str]
|
||||||
|
|
||||||
# Whether to pass the href prop to the child.
|
# Whether to pass the href prop to the child.
|
||||||
pass_href: Var[bool] = True # type: ignore
|
pass_href: Var[bool] = Var.create(True)
|
||||||
|
@ -18,8 +18,8 @@ try:
|
|||||||
Template = layout.Template
|
Template = layout.Template
|
||||||
except ImportError:
|
except ImportError:
|
||||||
console.warn("Plotly is not installed. Please run `pip install plotly`.")
|
console.warn("Plotly is not installed. Please run `pip install plotly`.")
|
||||||
Figure = Any # type: ignore
|
Figure = Any
|
||||||
Template = Any # type: ignore
|
Template = Any
|
||||||
|
|
||||||
|
|
||||||
def _event_points_data_signature(e0: Var) -> Tuple[Var[List[Point]]]:
|
def _event_points_data_signature(e0: Var) -> Tuple[Var[List[Point]]]:
|
||||||
@ -95,20 +95,20 @@ class Plotly(NoSSRComponent):
|
|||||||
|
|
||||||
library = "react-plotly.js@2.6.0"
|
library = "react-plotly.js@2.6.0"
|
||||||
|
|
||||||
lib_dependencies: List[str] = ["plotly.js@2.35.2"]
|
lib_dependencies: List[str] = ["plotly.js@2.35.3"]
|
||||||
|
|
||||||
tag = "Plot"
|
tag = "Plot"
|
||||||
|
|
||||||
is_default = True
|
is_default = True
|
||||||
|
|
||||||
# The figure to display. This can be a plotly figure or a plotly data json.
|
# The figure to display. This can be a plotly figure or a plotly data json.
|
||||||
data: Var[Figure] # type: ignore
|
data: Var[Figure] # pyright: ignore [reportInvalidTypeForm]
|
||||||
|
|
||||||
# The layout of the graph.
|
# The layout of the graph.
|
||||||
layout: Var[Dict]
|
layout: Var[Dict]
|
||||||
|
|
||||||
# The template for visual appearance of the graph.
|
# The template for visual appearance of the graph.
|
||||||
template: Var[Template] # type: ignore
|
template: Var[Template] # pyright: ignore [reportInvalidTypeForm]
|
||||||
|
|
||||||
# The config of the graph.
|
# The config of the graph.
|
||||||
config: Var[Dict]
|
config: Var[Dict]
|
||||||
|
@ -48,7 +48,7 @@ class PropsBase(Base):
|
|||||||
class NoExtrasAllowedProps(Base):
|
class NoExtrasAllowedProps(Base):
|
||||||
"""A class that holds props to be passed or applied to a component with no extra props allowed."""
|
"""A class that holds props to be passed or applied to a component with no extra props allowed."""
|
||||||
|
|
||||||
def __init__(self, component_name=None, **kwargs):
|
def __init__(self, component_name: str | None = None, **kwargs):
|
||||||
"""Initialize the props.
|
"""Initialize the props.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -62,13 +62,13 @@ class NoExtrasAllowedProps(Base):
|
|||||||
try:
|
try:
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
invalid_fields = ", ".join([error["loc"][0] for error in e.errors()]) # type: ignore
|
invalid_fields = ", ".join([error["loc"][0] for error in e.errors()]) # pyright: ignore [reportCallIssue, reportArgumentType]
|
||||||
supported_props_str = ", ".join(f'"{field}"' for field in self.get_fields())
|
supported_props_str = ", ".join(f'"{field}"' for field in self.get_fields())
|
||||||
raise InvalidPropValueError(
|
raise InvalidPropValueError(
|
||||||
f"Invalid prop(s) {invalid_fields} for {component_name!r}. Supported props are {supported_props_str}"
|
f"Invalid prop(s) {invalid_fields} for {component_name!r}. Supported props are {supported_props_str}"
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
class Config:
|
class Config: # pyright: ignore [reportIncompatibleVariableOverride]
|
||||||
"""Pydantic config."""
|
"""Pydantic config."""
|
||||||
|
|
||||||
arbitrary_types_allowed = True
|
arbitrary_types_allowed = True
|
||||||
|
@ -55,7 +55,7 @@ from .themes.layout.container import container as container
|
|||||||
from .themes.layout.flex import flex as flex
|
from .themes.layout.flex import flex as flex
|
||||||
from .themes.layout.grid import grid as grid
|
from .themes.layout.grid import grid as grid
|
||||||
from .themes.layout.list import list_item as list_item
|
from .themes.layout.list import list_item as list_item
|
||||||
from .themes.layout.list import list_ns as list # noqa
|
from .themes.layout.list import list_ns as list # noqa: F401
|
||||||
from .themes.layout.list import ordered_list as ordered_list
|
from .themes.layout.list import ordered_list as ordered_list
|
||||||
from .themes.layout.list import unordered_list as unordered_list
|
from .themes.layout.list import unordered_list as unordered_list
|
||||||
from .themes.layout.section import section as section
|
from .themes.layout.section import section as section
|
||||||
|
@ -10,6 +10,7 @@ from reflex.components.core.cond import cond
|
|||||||
from reflex.components.lucide.icon import Icon
|
from reflex.components.lucide.icon import Icon
|
||||||
from reflex.components.radix.primitives.base import RadixPrimitiveComponent
|
from reflex.components.radix.primitives.base import RadixPrimitiveComponent
|
||||||
from reflex.components.radix.themes.base import LiteralAccentColor, LiteralRadius
|
from reflex.components.radix.themes.base import LiteralAccentColor, LiteralRadius
|
||||||
|
from reflex.constants.compiler import MemoizationMode
|
||||||
from reflex.event import EventHandler
|
from reflex.event import EventHandler
|
||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
from reflex.vars import get_uuid_string_var
|
from reflex.vars import get_uuid_string_var
|
||||||
@ -196,8 +197,9 @@ class AccordionItem(AccordionComponent):
|
|||||||
|
|
||||||
# The header of the accordion item.
|
# The header of the accordion item.
|
||||||
header: Var[Union[Component, str]]
|
header: Var[Union[Component, str]]
|
||||||
|
|
||||||
# The content of the accordion item.
|
# The content of the accordion item.
|
||||||
content: Var[Union[Component, str]] = Var.create(None)
|
content: Var[Union[Component, str, None]] = Var.create(None)
|
||||||
|
|
||||||
_valid_children: List[str] = [
|
_valid_children: List[str] = [
|
||||||
"AccordionHeader",
|
"AccordionHeader",
|
||||||
@ -341,6 +343,8 @@ class AccordionTrigger(AccordionComponent):
|
|||||||
|
|
||||||
alias = "RadixAccordionTrigger"
|
alias = "RadixAccordionTrigger"
|
||||||
|
|
||||||
|
_memoization_mode = MemoizationMode(recursive=False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, *children, **props) -> Component:
|
def create(cls, *children, **props) -> Component:
|
||||||
"""Create the Accordion trigger component.
|
"""Create the Accordion trigger component.
|
||||||
@ -485,11 +489,11 @@ to {
|
|||||||
Returns:
|
Returns:
|
||||||
The style of the component.
|
The style of the component.
|
||||||
"""
|
"""
|
||||||
slideDown = LiteralVar.create(
|
slide_down = LiteralVar.create(
|
||||||
"${slideDown} var(--animation-duration) var(--animation-easing)",
|
"${slideDown} var(--animation-duration) var(--animation-easing)",
|
||||||
)
|
)
|
||||||
|
|
||||||
slideUp = LiteralVar.create(
|
slide_up = LiteralVar.create(
|
||||||
"${slideUp} var(--animation-duration) var(--animation-easing)",
|
"${slideUp} var(--animation-duration) var(--animation-easing)",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -503,8 +507,8 @@ to {
|
|||||||
"display": "block",
|
"display": "block",
|
||||||
"height": "var(--space-3)",
|
"height": "var(--space-3)",
|
||||||
},
|
},
|
||||||
"&[data-state='open']": {"animation": slideDown},
|
"&[data-state='open']": {"animation": slide_down},
|
||||||
"&[data-state='closed']": {"animation": slideUp},
|
"&[data-state='closed']": {"animation": slide_up},
|
||||||
_inherited_variant_selector("classic"): {
|
_inherited_variant_selector("classic"): {
|
||||||
"color": "var(--accent-contrast)",
|
"color": "var(--accent-contrast)",
|
||||||
},
|
},
|
||||||
|
@ -308,7 +308,9 @@ class AccordionItem(AccordionComponent):
|
|||||||
value: Optional[Union[Var[str], str]] = None,
|
value: Optional[Union[Var[str], str]] = None,
|
||||||
disabled: Optional[Union[Var[bool], bool]] = None,
|
disabled: Optional[Union[Var[bool], bool]] = None,
|
||||||
header: Optional[Union[Component, Var[Union[Component, str]], str]] = None,
|
header: Optional[Union[Component, Var[Union[Component, str]], str]] = None,
|
||||||
content: Optional[Union[Component, Var[Union[Component, str]], str]] = None,
|
content: Optional[
|
||||||
|
Union[Component, Var[Optional[Union[Component, str]]], str]
|
||||||
|
] = None,
|
||||||
color_scheme: Optional[
|
color_scheme: Optional[
|
||||||
Union[
|
Union[
|
||||||
Literal[
|
Literal[
|
||||||
|
@ -10,6 +10,7 @@ from reflex.components.component import Component, ComponentNamespace
|
|||||||
from reflex.components.radix.primitives.base import RadixPrimitiveComponent
|
from reflex.components.radix.primitives.base import RadixPrimitiveComponent
|
||||||
from reflex.components.radix.themes.base import Theme
|
from reflex.components.radix.themes.base import Theme
|
||||||
from reflex.components.radix.themes.layout.flex import Flex
|
from reflex.components.radix.themes.layout.flex import Flex
|
||||||
|
from reflex.constants.compiler import MemoizationMode
|
||||||
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
@ -66,7 +67,7 @@ class DrawerRoot(DrawerComponent):
|
|||||||
scroll_lock_timeout: Var[int]
|
scroll_lock_timeout: Var[int]
|
||||||
|
|
||||||
# When `True`, it prevents scroll restoration. Defaults to `True`.
|
# When `True`, it prevents scroll restoration. Defaults to `True`.
|
||||||
preventScrollRestoration: Var[bool]
|
prevent_scroll_restoration: Var[bool]
|
||||||
|
|
||||||
# Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
|
# Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
|
||||||
should_scale_background: Var[bool]
|
should_scale_background: Var[bool]
|
||||||
@ -83,7 +84,9 @@ class DrawerTrigger(DrawerComponent):
|
|||||||
alias = "Vaul" + tag
|
alias = "Vaul" + tag
|
||||||
|
|
||||||
# Defaults to true, if the first child acts as the trigger.
|
# Defaults to true, if the first child acts as the trigger.
|
||||||
as_child: Var[bool] = True # type: ignore
|
as_child: Var[bool] = Var.create(True)
|
||||||
|
|
||||||
|
_memoization_mode = MemoizationMode(recursive=False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, *children: Any, **props: Any) -> Component:
|
def create(cls, *children: Any, **props: Any) -> Component:
|
||||||
|
@ -81,7 +81,7 @@ class DrawerRoot(DrawerComponent):
|
|||||||
snap_points: Optional[List[Union[float, str]]] = None,
|
snap_points: Optional[List[Union[float, str]]] = None,
|
||||||
fade_from_index: Optional[Union[Var[int], int]] = None,
|
fade_from_index: Optional[Union[Var[int], int]] = None,
|
||||||
scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
|
scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
|
||||||
preventScrollRestoration: Optional[Union[Var[bool], bool]] = None,
|
prevent_scroll_restoration: Optional[Union[Var[bool], bool]] = None,
|
||||||
should_scale_background: Optional[Union[Var[bool], bool]] = None,
|
should_scale_background: Optional[Union[Var[bool], bool]] = None,
|
||||||
close_threshold: Optional[Union[Var[float], float]] = None,
|
close_threshold: Optional[Union[Var[float], float]] = None,
|
||||||
as_child: Optional[Union[Var[bool], bool]] = None,
|
as_child: Optional[Union[Var[bool], bool]] = None,
|
||||||
@ -129,7 +129,7 @@ class DrawerRoot(DrawerComponent):
|
|||||||
snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
|
snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
|
||||||
fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
|
fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
|
||||||
scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
|
scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
|
||||||
preventScrollRestoration: When `True`, it prevents scroll restoration. Defaults to `True`.
|
prevent_scroll_restoration: When `True`, it prevents scroll restoration. Defaults to `True`.
|
||||||
should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
|
should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
|
||||||
close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
|
close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
|
||||||
as_child: Change the default rendered element for the one passed as a child.
|
as_child: Change the default rendered element for the one passed as a child.
|
||||||
@ -567,7 +567,7 @@ class Drawer(ComponentNamespace):
|
|||||||
snap_points: Optional[List[Union[float, str]]] = None,
|
snap_points: Optional[List[Union[float, str]]] = None,
|
||||||
fade_from_index: Optional[Union[Var[int], int]] = None,
|
fade_from_index: Optional[Union[Var[int], int]] = None,
|
||||||
scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
|
scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
|
||||||
preventScrollRestoration: Optional[Union[Var[bool], bool]] = None,
|
prevent_scroll_restoration: Optional[Union[Var[bool], bool]] = None,
|
||||||
should_scale_background: Optional[Union[Var[bool], bool]] = None,
|
should_scale_background: Optional[Union[Var[bool], bool]] = None,
|
||||||
close_threshold: Optional[Union[Var[float], float]] = None,
|
close_threshold: Optional[Union[Var[float], float]] = None,
|
||||||
as_child: Optional[Union[Var[bool], bool]] = None,
|
as_child: Optional[Union[Var[bool], bool]] = None,
|
||||||
@ -615,7 +615,7 @@ class Drawer(ComponentNamespace):
|
|||||||
snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
|
snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
|
||||||
fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
|
fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
|
||||||
scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
|
scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
|
||||||
preventScrollRestoration: When `True`, it prevents scroll restoration. Defaults to `True`.
|
prevent_scroll_restoration: When `True`, it prevents scroll restoration. Defaults to `True`.
|
||||||
should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
|
should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
|
||||||
close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
|
close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
|
||||||
as_child: Change the default rendered element for the one passed as a child.
|
as_child: Change the default rendered element for the one passed as a child.
|
||||||
|
@ -132,10 +132,10 @@ class FormRoot(FormComponent, HTMLForm):
|
|||||||
on_submit: Optional[
|
on_submit: Optional[
|
||||||
Union[
|
Union[
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||||
],
|
],
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
@ -608,10 +608,10 @@ class Form(FormRoot):
|
|||||||
on_submit: Optional[
|
on_submit: Optional[
|
||||||
Union[
|
Union[
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||||
],
|
],
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
@ -741,10 +741,10 @@ class FormNamespace(ComponentNamespace):
|
|||||||
on_submit: Optional[
|
on_submit: Optional[
|
||||||
Union[
|
Union[
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||||
],
|
],
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
|
@ -83,7 +83,7 @@ class ProgressIndicator(ProgressComponent):
|
|||||||
"&[data_state='loading']": {
|
"&[data_state='loading']": {
|
||||||
"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms linear",
|
"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms linear",
|
||||||
},
|
},
|
||||||
"transform": f"translateX(calc(-100% + ({self.value} / {self.max} * 100%)))", # type: ignore
|
"transform": f"translateX(calc(-100% + ({self.value} / {self.max} * 100%)))",
|
||||||
"boxShadow": "inset 0 0 0 1px var(--gray-a5)",
|
"boxShadow": "inset 0 0 0 1px var(--gray-a5)",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ def on_value_event_spec(
|
|||||||
Returns:
|
Returns:
|
||||||
The event handler spec.
|
The event handler spec.
|
||||||
"""
|
"""
|
||||||
return (value,) # type: ignore
|
return (value,)
|
||||||
|
|
||||||
|
|
||||||
class SliderRoot(SliderComponent):
|
class SliderRoot(SliderComponent):
|
||||||
|
@ -17,7 +17,7 @@ rx.text(
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Dict, List, Literal, Optional, Union, get_args
|
from typing import Any, Dict, List, Literal, Optional, Union, get_args
|
||||||
|
|
||||||
from reflex.components.component import BaseComponent
|
from reflex.components.component import BaseComponent
|
||||||
from reflex.components.core.cond import Cond, color_mode_cond, cond
|
from reflex.components.core.cond import Cond, color_mode_cond, cond
|
||||||
@ -78,17 +78,19 @@ position_map: Dict[str, List[str]] = {
|
|||||||
|
|
||||||
|
|
||||||
# needed to inverse contains for find
|
# needed to inverse contains for find
|
||||||
def _find(const: List[str], var):
|
def _find(const: List[str], var: Any):
|
||||||
return LiteralArrayVar.create(const).contains(var)
|
return LiteralArrayVar.create(const).contains(var)
|
||||||
|
|
||||||
|
|
||||||
def _set_var_default(props, position, prop, default1, default2=""):
|
def _set_var_default(
|
||||||
|
props: dict, position: Any, prop: str, default1: str, default2: str = ""
|
||||||
|
):
|
||||||
props.setdefault(
|
props.setdefault(
|
||||||
prop, cond(_find(position_map[prop], position), default1, default2)
|
prop, cond(_find(position_map[prop], position), default1, default2)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _set_static_default(props, position, prop, default):
|
def _set_static_default(props: dict, position: Any, prop: str, default: str):
|
||||||
if prop in position:
|
if prop in position:
|
||||||
props.setdefault(prop, default)
|
props.setdefault(prop, default)
|
||||||
|
|
||||||
@ -115,12 +117,12 @@ class ColorModeIconButton(IconButton):
|
|||||||
Returns:
|
Returns:
|
||||||
The button component.
|
The button component.
|
||||||
"""
|
"""
|
||||||
position = props.pop("position", None)
|
position: str | Var = props.pop("position", None)
|
||||||
allow_system = props.pop("allow_system", False)
|
allow_system = props.pop("allow_system", False)
|
||||||
|
|
||||||
# position is used to set nice defaults for positioning the icon button
|
# position is used to set nice defaults for positioning the icon button
|
||||||
if isinstance(position, Var):
|
if isinstance(position, Var):
|
||||||
_set_var_default(props, position, "position", "fixed", position) # type: ignore
|
_set_var_default(props, position, "position", "fixed", position) # pyright: ignore [reportArgumentType]
|
||||||
_set_var_default(props, position, "bottom", "2rem")
|
_set_var_default(props, position, "bottom", "2rem")
|
||||||
_set_var_default(props, position, "top", "2rem")
|
_set_var_default(props, position, "top", "2rem")
|
||||||
_set_var_default(props, position, "left", "2rem")
|
_set_var_default(props, position, "left", "2rem")
|
||||||
@ -142,7 +144,7 @@ class ColorModeIconButton(IconButton):
|
|||||||
|
|
||||||
if allow_system:
|
if allow_system:
|
||||||
|
|
||||||
def color_mode_item(_color_mode):
|
def color_mode_item(_color_mode: str):
|
||||||
return dropdown_menu.item(
|
return dropdown_menu.item(
|
||||||
_color_mode.title(), on_click=set_color_mode(_color_mode)
|
_color_mode.title(), on_click=set_color_mode(_color_mode)
|
||||||
)
|
)
|
||||||
|
@ -5,6 +5,7 @@ from typing import Literal
|
|||||||
from reflex.components.component import ComponentNamespace
|
from reflex.components.component import ComponentNamespace
|
||||||
from reflex.components.core.breakpoints import Responsive
|
from reflex.components.core.breakpoints import Responsive
|
||||||
from reflex.components.el import elements
|
from reflex.components.el import elements
|
||||||
|
from reflex.constants.compiler import MemoizationMode
|
||||||
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
@ -33,6 +34,8 @@ class AlertDialogTrigger(RadixThemesTriggerComponent):
|
|||||||
|
|
||||||
tag = "AlertDialog.Trigger"
|
tag = "AlertDialog.Trigger"
|
||||||
|
|
||||||
|
_memoization_mode = MemoizationMode(recursive=False)
|
||||||
|
|
||||||
|
|
||||||
class AlertDialogContent(elements.Div, RadixThemesComponent):
|
class AlertDialogContent(elements.Div, RadixThemesComponent):
|
||||||
"""Contains the content of the dialog. This component is based on the div element."""
|
"""Contains the content of the dialog. This component is based on the div element."""
|
||||||
|
@ -20,7 +20,7 @@ class Card(elements.Div, RadixThemesComponent):
|
|||||||
# Card size: "1" - "5"
|
# Card size: "1" - "5"
|
||||||
size: Var[Responsive[Literal["1", "2", "3", "4", "5"],]]
|
size: Var[Responsive[Literal["1", "2", "3", "4", "5"],]]
|
||||||
|
|
||||||
# Variant of Card: "solid" | "soft" | "outline" | "ghost"
|
# Variant of Card: "surface" | "classic" | "ghost"
|
||||||
variant: Var[Literal["surface", "classic", "ghost"]]
|
variant: Var[Literal["surface", "classic", "ghost"]]
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ class Card(elements.Div, RadixThemesComponent):
|
|||||||
*children: Child components.
|
*children: Child components.
|
||||||
as_child: Change the default rendered element for the one passed as a child, merging their props and behavior.
|
as_child: Change the default rendered element for the one passed as a child, merging their props and behavior.
|
||||||
size: Card size: "1" - "5"
|
size: Card size: "1" - "5"
|
||||||
variant: Variant of Card: "solid" | "soft" | "outline" | "ghost"
|
variant: Variant of Card: "surface" | "classic" | "ghost"
|
||||||
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.
|
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.
|
content_editable: Indicates whether the element's content is editable.
|
||||||
|
@ -4,6 +4,7 @@ from typing import Dict, List, Literal, Union
|
|||||||
|
|
||||||
from reflex.components.component import ComponentNamespace
|
from reflex.components.component import ComponentNamespace
|
||||||
from reflex.components.core.breakpoints import Responsive
|
from reflex.components.core.breakpoints import Responsive
|
||||||
|
from reflex.constants.compiler import MemoizationMode
|
||||||
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
@ -55,6 +56,8 @@ class ContextMenuTrigger(RadixThemesComponent):
|
|||||||
|
|
||||||
_invalid_children: List[str] = ["ContextMenuContent"]
|
_invalid_children: List[str] = ["ContextMenuContent"]
|
||||||
|
|
||||||
|
_memoization_mode = MemoizationMode(recursive=False)
|
||||||
|
|
||||||
|
|
||||||
class ContextMenuContent(RadixThemesComponent):
|
class ContextMenuContent(RadixThemesComponent):
|
||||||
"""The component that pops out when the context menu is open."""
|
"""The component that pops out when the context menu is open."""
|
||||||
@ -153,6 +156,8 @@ class ContextMenuSubTrigger(RadixThemesComponent):
|
|||||||
|
|
||||||
_valid_parents: List[str] = ["ContextMenuContent", "ContextMenuSub"]
|
_valid_parents: List[str] = ["ContextMenuContent", "ContextMenuSub"]
|
||||||
|
|
||||||
|
_memoization_mode = MemoizationMode(recursive=False)
|
||||||
|
|
||||||
|
|
||||||
class ContextMenuSubContent(RadixThemesComponent):
|
class ContextMenuSubContent(RadixThemesComponent):
|
||||||
"""The component that pops out when a submenu is open."""
|
"""The component that pops out when a submenu is open."""
|
||||||
|
@ -5,6 +5,7 @@ from typing import Literal
|
|||||||
from reflex.components.component import ComponentNamespace
|
from reflex.components.component import ComponentNamespace
|
||||||
from reflex.components.core.breakpoints import Responsive
|
from reflex.components.core.breakpoints import Responsive
|
||||||
from reflex.components.el import elements
|
from reflex.components.el import elements
|
||||||
|
from reflex.constants.compiler import MemoizationMode
|
||||||
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
@ -31,6 +32,8 @@ class DialogTrigger(RadixThemesTriggerComponent):
|
|||||||
|
|
||||||
tag = "Dialog.Trigger"
|
tag = "Dialog.Trigger"
|
||||||
|
|
||||||
|
_memoization_mode = MemoizationMode(recursive=False)
|
||||||
|
|
||||||
|
|
||||||
class DialogTitle(RadixThemesComponent):
|
class DialogTitle(RadixThemesComponent):
|
||||||
"""Title component to display inside a Dialog modal."""
|
"""Title component to display inside a Dialog modal."""
|
||||||
|
@ -4,6 +4,7 @@ from typing import Dict, List, Literal, Union
|
|||||||
|
|
||||||
from reflex.components.component import ComponentNamespace
|
from reflex.components.component import ComponentNamespace
|
||||||
from reflex.components.core.breakpoints import Responsive
|
from reflex.components.core.breakpoints import Responsive
|
||||||
|
from reflex.constants.compiler import MemoizationMode
|
||||||
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
@ -60,6 +61,8 @@ class DropdownMenuTrigger(RadixThemesTriggerComponent):
|
|||||||
|
|
||||||
_invalid_children: List[str] = ["DropdownMenuContent"]
|
_invalid_children: List[str] = ["DropdownMenuContent"]
|
||||||
|
|
||||||
|
_memoization_mode = MemoizationMode(recursive=False)
|
||||||
|
|
||||||
|
|
||||||
class DropdownMenuContent(RadixThemesComponent):
|
class DropdownMenuContent(RadixThemesComponent):
|
||||||
"""The Dropdown Menu Content component that pops out when the dropdown menu is open."""
|
"""The Dropdown Menu Content component that pops out when the dropdown menu is open."""
|
||||||
@ -143,6 +146,8 @@ class DropdownMenuSubTrigger(RadixThemesTriggerComponent):
|
|||||||
|
|
||||||
_valid_parents: List[str] = ["DropdownMenuContent", "DropdownMenuSub"]
|
_valid_parents: List[str] = ["DropdownMenuContent", "DropdownMenuSub"]
|
||||||
|
|
||||||
|
_memoization_mode = MemoizationMode(recursive=False)
|
||||||
|
|
||||||
|
|
||||||
class DropdownMenuSub(RadixThemesComponent):
|
class DropdownMenuSub(RadixThemesComponent):
|
||||||
"""Contains all the parts of a submenu."""
|
"""Contains all the parts of a submenu."""
|
||||||
|
@ -5,6 +5,7 @@ from typing import Dict, Literal, Union
|
|||||||
from reflex.components.component import ComponentNamespace
|
from reflex.components.component import ComponentNamespace
|
||||||
from reflex.components.core.breakpoints import Responsive
|
from reflex.components.core.breakpoints import Responsive
|
||||||
from reflex.components.el import elements
|
from reflex.components.el import elements
|
||||||
|
from reflex.constants.compiler import MemoizationMode
|
||||||
from reflex.event import EventHandler, passthrough_event_spec
|
from reflex.event import EventHandler, passthrough_event_spec
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
@ -37,6 +38,8 @@ class HoverCardTrigger(RadixThemesTriggerComponent):
|
|||||||
|
|
||||||
tag = "HoverCard.Trigger"
|
tag = "HoverCard.Trigger"
|
||||||
|
|
||||||
|
_memoization_mode = MemoizationMode(recursive=False)
|
||||||
|
|
||||||
|
|
||||||
class HoverCardContent(elements.Div, RadixThemesComponent):
|
class HoverCardContent(elements.Div, RadixThemesComponent):
|
||||||
"""Contains the content of the open hover card."""
|
"""Contains the content of the open hover card."""
|
||||||
|
@ -22,6 +22,8 @@ from ..base import (
|
|||||||
|
|
||||||
LiteralButtonSize = Literal["1", "2", "3", "4"]
|
LiteralButtonSize = Literal["1", "2", "3", "4"]
|
||||||
|
|
||||||
|
RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
|
||||||
|
|
||||||
|
|
||||||
class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
|
class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
|
||||||
"""A button designed specifically for usage with a single icon."""
|
"""A button designed specifically for usage with a single icon."""
|
||||||
@ -72,8 +74,6 @@ class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
|
|||||||
"IconButton requires a child icon. Pass a string as the first child or a rx.icon."
|
"IconButton requires a child icon. Pass a string as the first child or a rx.icon."
|
||||||
)
|
)
|
||||||
if "size" in props:
|
if "size" in props:
|
||||||
RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
|
|
||||||
|
|
||||||
if isinstance(props["size"], str):
|
if isinstance(props["size"], str):
|
||||||
children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]]
|
children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]]
|
||||||
else:
|
else:
|
||||||
|
@ -14,6 +14,7 @@ from reflex.vars.base import Var
|
|||||||
from ..base import RadixLoadingProp, RadixThemesComponent
|
from ..base import RadixLoadingProp, RadixThemesComponent
|
||||||
|
|
||||||
LiteralButtonSize = Literal["1", "2", "3", "4"]
|
LiteralButtonSize = Literal["1", "2", "3", "4"]
|
||||||
|
RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
|
||||||
|
|
||||||
class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
|
class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
|
||||||
@overload
|
@overload
|
||||||
|
@ -5,6 +5,7 @@ from typing import Dict, Literal, Union
|
|||||||
from reflex.components.component import ComponentNamespace
|
from reflex.components.component import ComponentNamespace
|
||||||
from reflex.components.core.breakpoints import Responsive
|
from reflex.components.core.breakpoints import Responsive
|
||||||
from reflex.components.el import elements
|
from reflex.components.el import elements
|
||||||
|
from reflex.constants.compiler import MemoizationMode
|
||||||
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
@ -34,6 +35,8 @@ class PopoverTrigger(RadixThemesTriggerComponent):
|
|||||||
|
|
||||||
tag = "Popover.Trigger"
|
tag = "Popover.Trigger"
|
||||||
|
|
||||||
|
_memoization_mode = MemoizationMode(recursive=False)
|
||||||
|
|
||||||
|
|
||||||
class PopoverContent(elements.Div, RadixThemesComponent):
|
class PopoverContent(elements.Div, RadixThemesComponent):
|
||||||
"""Contains content to be rendered in the open popover."""
|
"""Contains content to be rendered in the open popover."""
|
||||||
|
@ -85,6 +85,8 @@ class RadioCardsItem(RadixThemesComponent):
|
|||||||
# When true, indicates that the user must check the radio item before the owning form can be submitted.
|
# When true, indicates that the user must check the radio item before the owning form can be submitted.
|
||||||
required: Var[bool]
|
required: Var[bool]
|
||||||
|
|
||||||
|
_valid_parents: list[str] = ["RadioCardsRoot"]
|
||||||
|
|
||||||
|
|
||||||
class RadioCards(SimpleNamespace):
|
class RadioCards(SimpleNamespace):
|
||||||
"""RadioCards components namespace."""
|
"""RadioCards components namespace."""
|
||||||
|
@ -155,7 +155,7 @@ class HighLevelRadioGroup(RadixThemesComponent):
|
|||||||
if isinstance(default_value, str) or (
|
if isinstance(default_value, str) or (
|
||||||
isinstance(default_value, Var) and default_value._var_type is str
|
isinstance(default_value, Var) and default_value._var_type is str
|
||||||
):
|
):
|
||||||
default_value = LiteralVar.create(default_value) # type: ignore
|
default_value = LiteralVar.create(default_value)
|
||||||
else:
|
else:
|
||||||
default_value = LiteralVar.create(default_value).to_string()
|
default_value = LiteralVar.create(default_value).to_string()
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ from typing import List, Literal, Union
|
|||||||
import reflex as rx
|
import reflex as rx
|
||||||
from reflex.components.component import Component, ComponentNamespace
|
from reflex.components.component import Component, ComponentNamespace
|
||||||
from reflex.components.core.breakpoints import Responsive
|
from reflex.components.core.breakpoints import Responsive
|
||||||
|
from reflex.constants.compiler import MemoizationMode
|
||||||
from reflex.event import no_args_event_spec, passthrough_event_spec
|
from reflex.event import no_args_event_spec, passthrough_event_spec
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
@ -69,6 +70,8 @@ class SelectTrigger(RadixThemesComponent):
|
|||||||
|
|
||||||
_valid_parents: List[str] = ["SelectRoot"]
|
_valid_parents: List[str] = ["SelectRoot"]
|
||||||
|
|
||||||
|
_memoization_mode = MemoizationMode(recursive=False)
|
||||||
|
|
||||||
|
|
||||||
class SelectContent(RadixThemesComponent):
|
class SelectContent(RadixThemesComponent):
|
||||||
"""The component that pops out when the select is open."""
|
"""The component that pops out when the select is open."""
|
||||||
|
@ -7,6 +7,7 @@ from typing import Any, Dict, List, Literal
|
|||||||
from reflex.components.component import Component, ComponentNamespace
|
from reflex.components.component import Component, ComponentNamespace
|
||||||
from reflex.components.core.breakpoints import Responsive
|
from reflex.components.core.breakpoints import Responsive
|
||||||
from reflex.components.core.colors import color
|
from reflex.components.core.colors import color
|
||||||
|
from reflex.constants.compiler import MemoizationMode
|
||||||
from reflex.event import EventHandler, passthrough_event_spec
|
from reflex.event import EventHandler, passthrough_event_spec
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
@ -95,6 +96,8 @@ class TabsTrigger(RadixThemesComponent):
|
|||||||
|
|
||||||
_valid_parents: List[str] = ["TabsList"]
|
_valid_parents: List[str] = ["TabsList"]
|
||||||
|
|
||||||
|
_memoization_mode = MemoizationMode(recursive=False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, *children, **props) -> Component:
|
def create(cls, *children, **props) -> Component:
|
||||||
"""Create a TabsTrigger component.
|
"""Create a TabsTrigger component.
|
||||||
|
@ -96,5 +96,17 @@ class TextArea(RadixThemesComponent, elements.Textarea):
|
|||||||
return DebounceInput.create(super().create(*children, **props))
|
return DebounceInput.create(super().create(*children, **props))
|
||||||
return super().create(*children, **props)
|
return super().create(*children, **props)
|
||||||
|
|
||||||
|
def add_style(self):
|
||||||
|
"""Add the style to the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The style of the component.
|
||||||
|
"""
|
||||||
|
added_style: dict[str, dict] = {}
|
||||||
|
added_style.setdefault("& textarea", {})
|
||||||
|
if "padding" in self.style:
|
||||||
|
added_style["& textarea"]["padding"] = self.style.pop("padding")
|
||||||
|
return added_style
|
||||||
|
|
||||||
|
|
||||||
text_area = TextArea.create
|
text_area = TextArea.create
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user