Merge remote-tracking branch 'upstream/main' into state-compression

This commit is contained in:
Benedikt Bartscher 2025-01-30 15:28:08 +01:00
commit 9d40c25a9c
No known key found for this signature in database
216 changed files with 3590 additions and 2989 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ assets/external/*
dist/* dist/*
examples/ examples/
.web .web
.states
.idea .idea
.vscode .vscode
.coverage .coverage

View File

@ -28,7 +28,7 @@ repos:
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.392
hooks: hooks:
- id: pyright - id: pyright
args: [reflex, tests] args: [reflex, tests]

View File

@ -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

View File

@ -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

View File

@ -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.")

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -35,7 +35,7 @@ Reflex के अंदर के कामकाज को जानने क
## ⚙️ इंस्टॉलेशन (Installation) ## ⚙️ इंस्टॉलेशन (Installation)
एक टर्मिनल खोलें और चलाएं (Python 3.9+ की आवश्यकता है): एक टर्मिनल खोलें और चलाएं (Python 3.10+ की आवश्यकता है):
```bash ```bash
pip install reflex pip install reflex

View File

@ -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

View File

@ -37,7 +37,7 @@ Reflex がどのように動作しているかを知るには、[アーキテク
## ⚙️ インストール ## ⚙️ インストール
ターミナルを開いて以下のコマンドを実行してください。Python 3.9 以上が必要です。): ターミナルを開いて以下のコマンドを実行してください。Python 3.10 以上が必要です。):
```bash ```bash
pip install reflex pip install reflex

View File

@ -20,7 +20,7 @@
--- ---
## ⚙️ 설치 ## ⚙️ 설치
터미널을 열고 실행하세요. (Python 3.9+ 필요): 터미널을 열고 실행하세요. (Python 3.10+ 필요):
```bash ```bash
pip install reflex pip install reflex

View File

@ -34,7 +34,7 @@
## ⚙️ Installation - نصب و راه اندازی ## ⚙️ Installation - نصب و راه اندازی
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.9+): یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.10+):
```bash ```bash
pip install reflex pip install reflex

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -34,7 +34,7 @@ Reflex 是一个使用纯Python构建全栈web应用的库。
## ⚙️ 安装 ## ⚙️ 安装
打开一个终端并且运行(要求Python3.9+): 打开一个终端并且运行(要求Python3.10+):
```bash ```bash
pip install reflex pip install reflex

View File

@ -36,7 +36,7 @@ Reflex 是一個可以用純 Python 構建全端網頁應用程式的函式庫
## ⚙️ 安裝 ## ⚙️ 安裝
開啟一個終端機並且執行 (需要 Python 3.9+): 開啟一個終端機並且執行 (需要 Python 3.10+):
```bash ```bash
pip install reflex pip install reflex

1687
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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,13 +50,12 @@ 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"
@ -82,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", "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]
@ -103,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"

View File

@ -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"]

View File

@ -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;
} }
})() })()

View File

@ -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);

View File

@ -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";
@ -407,6 +408,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

View File

@ -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)

View File

@ -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

View File

@ -27,6 +27,7 @@ from typing import (
Dict, Dict,
Generic, Generic,
List, List,
MutableMapping,
Optional, Optional,
Set, Set,
Type, Type,
@ -145,7 +146,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))
@ -251,36 +252,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 +293,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 +330,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 +353,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 +363,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 +400,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 +446,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 +492,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 +525,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 +583,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 +602,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 +620,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 +636,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 +651,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 +675,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 +712,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 +736,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 +780,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 +843,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 +858,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,11 +891,11 @@ 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:
@ -857,13 +921,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,14 +943,16 @@ 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: should_compile = self._should_compile()
for route in self._unevaluated_pages:
console.debug(f"Evaluating page: {route}") console.debug(f"Evaluating page: {route}")
self._compile_page(route) self._compile_page(route, save_page=should_compile)
# Add the optional endpoints (_upload) # Add the optional endpoints (_upload)
self._add_optional_endpoints() self._add_optional_endpoints()
if not self._should_compile(): if not should_compile:
return return
self._validate_var_dependencies() self._validate_var_dependencies()
@ -906,7 +972,7 @@ class App(MiddlewareMixin, LifespanMixin):
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)
+ fixed_pages_within_executor + fixed_pages_within_executor
+ adhoc_steps_without_executor, + adhoc_steps_without_executor,
) )
@ -925,7 +991,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 +1006,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 +1025,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 +1048,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 +1096,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 +1218,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 +1230,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 +1242,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__
@ -1320,15 +1387,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 +1589,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 +1624,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 +1633,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 +1691,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:

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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})

View File

@ -2,6 +2,8 @@
from __future__ import annotations from __future__ import annotations
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
@ -12,7 +14,9 @@ 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 (
@ -115,7 +119,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,8 +167,12 @@ 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
@ -494,7 +502,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:

View File

@ -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.

View File

@ -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"

View File

@ -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:
@ -740,22 +702,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 +759,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 +851,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 +869,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 +909,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 +1672,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 +1705,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 +1800,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.
@ -2428,7 +2389,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 +2398,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,
) )

View File

@ -25,7 +25,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 +99,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 +116,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(

View File

@ -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
} }
) )

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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
@ -97,9 +98,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",

View File

@ -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):

View File

@ -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]
), ),
) )

View File

@ -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(

View File

@ -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(

View File

@ -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,7 +345,7 @@ 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])" "{"]

View File

@ -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",

View File

@ -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", {}))
@ -653,8 +653,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 +758,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(

View File

@ -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))

View File

@ -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)

View File

@ -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:

View File

@ -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(""),

View File

@ -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,

View File

@ -2,13 +2,15 @@
from reflex.components.component import Component from reflex.components.component import Component
from reflex.utils import format from reflex.utils import format
from reflex.vars.base import Var from reflex.utils.imports import ImportVar
from reflex.vars.base import LiteralVar, Var
from reflex.vars.sequence import LiteralStringVar
class LucideIconComponent(Component): class LucideIconComponent(Component):
"""Lucide Icon Component.""" """Lucide Icon Component."""
library = "lucide-react@0.469.0" library = "lucide-react@0.471.1"
class Icon(LucideIconComponent): class Icon(LucideIconComponent):
@ -32,6 +34,7 @@ class Icon(LucideIconComponent):
Raises: Raises:
AttributeError: The errors tied to bad usage of the Icon component. AttributeError: The errors tied to bad usage of the Icon component.
ValueError: If the icon tag is invalid. ValueError: If the icon tag is invalid.
TypeError: If the icon name is not a string.
Returns: Returns:
The created component. The created component.
@ -39,7 +42,6 @@ class Icon(LucideIconComponent):
if children: if children:
if len(children) == 1 and isinstance(children[0], str): if len(children) == 1 and isinstance(children[0], str):
props["tag"] = children[0] props["tag"] = children[0]
children = []
else: else:
raise AttributeError( raise AttributeError(
f"Passing multiple children to Icon component is not allowed: remove positional arguments {children[1:]} to fix" f"Passing multiple children to Icon component is not allowed: remove positional arguments {children[1:]} to fix"
@ -47,24 +49,46 @@ class Icon(LucideIconComponent):
if "tag" not in props: if "tag" not in props:
raise AttributeError("Missing 'tag' keyword-argument for Icon") raise AttributeError("Missing 'tag' keyword-argument for Icon")
tag: str | Var | LiteralVar = props.pop("tag")
if isinstance(tag, LiteralVar):
if isinstance(tag, LiteralStringVar):
tag = tag._var_value
else:
raise TypeError(f"Icon name must be a string, got {type(tag)}")
elif isinstance(tag, Var):
return DynamicIcon.create(name=tag, **props)
if ( if (
not isinstance(props["tag"], str) not isinstance(tag, str)
or format.to_snake_case(props["tag"]) not in LUCIDE_ICON_LIST or format.to_snake_case(tag) not in LUCIDE_ICON_LIST
): ):
raise ValueError( raise ValueError(
f"Invalid icon tag: {props['tag']}. Please use one of the following: {', '.join(LUCIDE_ICON_LIST[0:25])}, ..." f"Invalid icon tag: {tag}. Please use one of the following: {', '.join(LUCIDE_ICON_LIST[0:25])}, ..."
"\nSee full list at https://lucide.dev/icons." "\nSee full list at https://lucide.dev/icons."
) )
if props["tag"] in LUCIDE_ICON_MAPPING_OVERRIDE: if tag in LUCIDE_ICON_MAPPING_OVERRIDE:
props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[props["tag"]] props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[tag]
else: else:
props["tag"] = ( props["tag"] = format.to_title_case(format.to_snake_case(tag)) + "Icon"
format.to_title_case(format.to_snake_case(props["tag"])) + "Icon"
)
props["alias"] = f"Lucide{props['tag']}" props["alias"] = f"Lucide{props['tag']}"
props.setdefault("color", "var(--current-color)") props.setdefault("color", "var(--current-color)")
return super().create(*children, **props) return super().create(**props)
class DynamicIcon(LucideIconComponent):
"""A DynamicIcon component."""
tag = "DynamicIcon"
name: Var[str]
def _get_imports(self):
_imports = super()._get_imports()
if self.library:
_imports.pop(self.library)
_imports["lucide-react/dynamic"] = [ImportVar("DynamicIcon", install=False)]
return _imports
LUCIDE_ICON_LIST = [ LUCIDE_ICON_LIST = [
@ -846,6 +870,7 @@ LUCIDE_ICON_LIST = [
"house", "house",
"house_plug", "house_plug",
"house_plus", "house_plus",
"house_wifi",
"ice_cream_bowl", "ice_cream_bowl",
"ice_cream_cone", "ice_cream_cone",
"id_card", "id_card",
@ -1534,6 +1559,7 @@ LUCIDE_ICON_LIST = [
"trending_up_down", "trending_up_down",
"triangle", "triangle",
"triangle_alert", "triangle_alert",
"triangle_dashed",
"triangle_right", "triangle_right",
"trophy", "trophy",
"truck", "truck",

View File

@ -104,12 +104,60 @@ class Icon(LucideIconComponent):
Raises: Raises:
AttributeError: The errors tied to bad usage of the Icon component. AttributeError: The errors tied to bad usage of the Icon component.
ValueError: If the icon tag is invalid. ValueError: If the icon tag is invalid.
TypeError: If the icon name is not a string.
Returns: Returns:
The created component. The created component.
""" """
... ...
class DynamicIcon(LucideIconComponent):
@overload
@classmethod
def create( # type: ignore
cls,
*children,
name: Optional[Union[Var[str], 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,
) -> "DynamicIcon":
"""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.
"""
...
LUCIDE_ICON_LIST = [ LUCIDE_ICON_LIST = [
"a_arrow_down", "a_arrow_down",
"a_arrow_up", "a_arrow_up",
@ -889,6 +937,7 @@ LUCIDE_ICON_LIST = [
"house", "house",
"house_plug", "house_plug",
"house_plus", "house_plus",
"house_wifi",
"ice_cream_bowl", "ice_cream_bowl",
"ice_cream_cone", "ice_cream_cone",
"id_card", "id_card",
@ -1577,6 +1626,7 @@ LUCIDE_ICON_LIST = [
"trending_up_down", "trending_up_down",
"triangle", "triangle",
"triangle_alert", "triangle_alert",
"triangle_dashed",
"triangle_right", "triangle_right",
"trophy", "trophy",
"truck", "truck",

View File

@ -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}

View File

@ -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).

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -196,8 +196,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",
@ -485,11 +486,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 +504,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)",
}, },

View File

@ -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[

View File

@ -66,7 +66,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 +83,7 @@ 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)
@classmethod @classmethod
def create(cls, *children: Any, **props: Any) -> Component: def create(cls, *children: Any, **props: Any) -> Component:

View File

@ -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.

View File

@ -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,

View File

@ -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)",
} }

View File

@ -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):

View File

@ -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)
) )

View File

@ -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:

View File

@ -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

View File

@ -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."""

View File

@ -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()

View File

@ -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

View File

@ -268,4 +268,6 @@ class TextArea(RadixThemesComponent, elements.Textarea):
""" """
... ...
def add_style(self): ...
text_area = TextArea.create text_area = TextArea.create

View File

@ -105,7 +105,7 @@ class TextFieldRoot(elements.Input, RadixThemesComponent):
(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(""),

View File

@ -28,6 +28,9 @@ LiteralStickyType = Literal[
] ]
ARIA_LABEL_KEY = "aria_label"
# The Tooltip inherits props from the Tooltip.Root, Tooltip.Portal, Tooltip.Content # The Tooltip inherits props from the Tooltip.Root, Tooltip.Portal, Tooltip.Content
class Tooltip(RadixThemesComponent): class Tooltip(RadixThemesComponent):
"""Floating element that provides a control with contextual information via pointer or focus.""" """Floating element that provides a control with contextual information via pointer or focus."""
@ -104,7 +107,6 @@ class Tooltip(RadixThemesComponent):
Returns: Returns:
The created component. The created component.
""" """
ARIA_LABEL_KEY = "aria_label"
if props.get(ARIA_LABEL_KEY) is not None: if props.get(ARIA_LABEL_KEY) is not None:
props[format.to_kebab_case(ARIA_LABEL_KEY)] = props.pop(ARIA_LABEL_KEY) props[format.to_kebab_case(ARIA_LABEL_KEY)] = props.pop(ARIA_LABEL_KEY)

View File

@ -14,6 +14,7 @@ from ..base import RadixThemesComponent
LiteralSideType = Literal["top", "right", "bottom", "left"] LiteralSideType = Literal["top", "right", "bottom", "left"]
LiteralAlignType = Literal["start", "center", "end"] LiteralAlignType = Literal["start", "center", "end"]
LiteralStickyType = Literal["partial", "always"] LiteralStickyType = Literal["partial", "always"]
ARIA_LABEL_KEY = "aria_label"
class Tooltip(RadixThemesComponent): class Tooltip(RadixThemesComponent):
@overload @overload

View File

@ -9,7 +9,7 @@ from .container import container as container
from .flex import flex as flex from .flex import flex as flex
from .grid import grid as grid from .grid import grid as grid
from .list import list_item as list_item from .list import list_item as list_item
from .list import list_ns as list # noqa from .list import list_ns as list # noqa: F401
from .list import ordered_list as ordered_list from .list import ordered_list as ordered_list
from .list import unordered_list as unordered_list from .list import unordered_list as unordered_list
from .section import section as section from .section import section as section

View File

@ -72,7 +72,7 @@ class BaseList(Component, MarkdownComponentMap):
if isinstance(items, Var): if isinstance(items, Var):
children = [Foreach.create(items, ListItem.create)] children = [Foreach.create(items, ListItem.create)]
else: else:
children = [ListItem.create(item) for item in items] # type: ignore children = [ListItem.create(item) for item in items]
props["direction"] = "column" props["direction"] = "column"
style = props.setdefault("style", {}) style = props.setdefault("style", {})
style["list_style_type"] = list_style_type style["list_style_type"] = list_style_type
@ -189,7 +189,7 @@ ordered_list = list_ns.ordered
unordered_list = list_ns.unordered unordered_list = list_ns.unordered
def __getattr__(name): def __getattr__(name: Any):
# special case for when accessing list to avoid shadowing # special case for when accessing list to avoid shadowing
# python's built in list object. # python's built in list object.
if name == "list": if name == "list":

View File

@ -49,14 +49,14 @@ class VStack(Stack):
"""A vertical stack component.""" """A vertical stack component."""
# The direction of the stack. # The direction of the stack.
direction: Var[LiteralFlexDirection] = "column" # type: ignore direction: Var[LiteralFlexDirection] = Var.create("column")
class HStack(Stack): class HStack(Stack):
"""A horizontal stack component.""" """A horizontal stack component."""
# The direction of the stack. # The direction of the stack.
direction: Var[LiteralFlexDirection] = "row" # type: ignore direction: Var[LiteralFlexDirection] = Var.create("row")
stack = Stack.create stack = Stack.create

View File

@ -60,7 +60,7 @@ class Link(RadixThemesComponent, A, MemoizationLeaf, MarkdownComponentMap):
Returns: Returns:
The import dict. The import dict.
""" """
return next_link._get_imports() # type: ignore return next_link._get_imports() # pyright: ignore [reportReturnType]
@classmethod @classmethod
def create(cls, *children, **props) -> Component: def create(cls, *children, **props) -> Component:

View File

@ -47,7 +47,7 @@ class Text(elements.Span, RadixThemesComponent, MarkdownComponentMap):
as_child: Var[bool] as_child: Var[bool]
# Change the default rendered element into a semantically appropriate alternative (cannot be used with asChild) # Change the default rendered element into a semantically appropriate alternative (cannot be used with asChild)
as_: Var[LiteralType] = "p" # type: ignore as_: Var[LiteralType] = Var.create("p")
# Text size: "1" - "9" # Text size: "1" - "9"
size: Var[Responsive[LiteralTextSize]] size: Var[Responsive[LiteralTextSize]]
@ -71,7 +71,7 @@ class Text(elements.Span, RadixThemesComponent, MarkdownComponentMap):
class Span(Text): class Span(Text):
"""A variant of text rendering as <span> element.""" """A variant of text rendering as <span> element."""
as_: Var[LiteralType] = "span" # type: ignore as_: Var[LiteralType] = Var.create("span")
class Em(elements.Em, RadixThemesComponent): class Em(elements.Em, RadixThemesComponent):

View File

@ -39,7 +39,7 @@ class ReactPlayer(NoSSRComponent):
loop: Var[bool] loop: Var[bool]
# Set to true or false to display native player controls. # Set to true or false to display native player controls.
controls: Var[bool] = True # type: ignore controls: Var[bool] = Var.create(True)
# Set to true to show just the video thumbnail, which loads the full player on click # Set to true to show just the video thumbnail, which loads the full player on click
light: Var[bool] light: Var[bool]

View File

@ -70,6 +70,8 @@ _SUBMOD_ATTRS: dict = {
"Label", "Label",
"label_list", "label_list",
"LabelList", "LabelList",
"cell",
"Cell",
], ],
"polar": [ "polar": [
"pie", "pie",

View File

@ -53,11 +53,13 @@ from .charts import radar_chart as radar_chart
from .charts import radial_bar_chart as radial_bar_chart from .charts import radial_bar_chart as radial_bar_chart
from .charts import scatter_chart as scatter_chart from .charts import scatter_chart as scatter_chart
from .charts import treemap as treemap from .charts import treemap as treemap
from .general import Cell as Cell
from .general import GraphingTooltip as GraphingTooltip from .general import GraphingTooltip as GraphingTooltip
from .general import Label as Label from .general import Label as Label
from .general import LabelList as LabelList from .general import LabelList as LabelList
from .general import Legend as Legend from .general import Legend as Legend
from .general import ResponsiveContainer as ResponsiveContainer from .general import ResponsiveContainer as ResponsiveContainer
from .general import cell as cell
from .general import graphing_tooltip as graphing_tooltip from .general import graphing_tooltip as graphing_tooltip
from .general import label as label from .general import label as label
from .general import label_list as label_list from .general import label_list as label_list

View File

@ -25,10 +25,10 @@ class ChartBase(RechartsCharts):
"""A component that wraps a Recharts charts.""" """A component that wraps a Recharts charts."""
# The width of chart container. String or Integer # The width of chart container. String or Integer
width: Var[Union[str, int]] = "100%" # type: ignore width: Var[Union[str, int]] = Var.create("100%")
# The height of chart container. # The height of chart container.
height: Var[Union[str, int]] = "100%" # type: ignore height: Var[Union[str, int]] = Var.create("100%")
# The customized event handler of click on the component in this chart # The customized event handler of click on the component in this chart
on_click: EventHandler[no_args_event_spec] on_click: EventHandler[no_args_event_spec]
@ -69,7 +69,7 @@ class ChartBase(RechartsCharts):
) )
@classmethod @classmethod
def create(cls, *children, **props) -> Component: def create(cls, *children: Any, **props: Any) -> Component:
"""Create a chart component. """Create a chart component.
Args: Args:
@ -84,19 +84,19 @@ class ChartBase(RechartsCharts):
cls._ensure_valid_dimension("width", width) cls._ensure_valid_dimension("width", width)
cls._ensure_valid_dimension("height", height) cls._ensure_valid_dimension("height", height)
dim_props = { # Ensure that the min_height and min_width are set to prevent the chart from collapsing.
"width": width if width is not None else "100%", # We are using small values so that height and width can still be used over min_height and min_width.
"height": height if height is not None else "100%", # Without this, sometimes the chart will not be visible. Causing confusion to the user.
} # With this, the user will see a small chart and can adjust the height and width and can figure out that the issue is with the size.
# Provide min dimensions so the graph always appears, even if the outer container is zero-size. min_height = props.pop("min_height", 10)
if width is None: min_width = props.pop("min_width", 10)
dim_props["min_width"] = 200
if height is None:
dim_props["min_height"] = 100
return ResponsiveContainer.create( return ResponsiveContainer.create(
super().create(*children, **props), super().create(*children, **props),
**dim_props, # type: ignore width=width if width is not None else "100%",
height=height if height is not None else "100%",
min_width=min_width,
min_height=min_height,
) )
@ -458,10 +458,10 @@ class Treemap(RechartsCharts):
alias = "RechartsTreemap" alias = "RechartsTreemap"
# The width of chart container. String or Integer. Default: "100%" # The width of chart container. String or Integer. Default: "100%"
width: Var[Union[str, int]] = "100%" # type: ignore width: Var[Union[str, int]] = Var.create("100%")
# The height of chart container. String or Integer. Default: "100%" # The height of chart container. String or Integer. Default: "100%"
height: Var[Union[str, int]] = "100%" # type: ignore height: Var[Union[str, int]] = Var.create("100%")
# data of treemap. Array # data of treemap. Array
data: Var[List[Dict[str, Any]]] data: Var[List[Dict[str, Any]]]

View File

@ -36,11 +36,11 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
# The height of chart container. Can be a number or string. Default: "100%" # The height of chart container. Can be a number or string. Default: "100%"
height: Var[Union[int, str]] height: Var[Union[int, str]]
# The minimum width of chart container. Number # The minimum width of chart container. Number or string.
min_width: Var[int] min_width: Var[Union[int, str]]
# The minimum height of chart container. Number # The minimum height of chart container. Number or string.
min_height: Var[int] min_height: Var[Union[int, str]]
# If specified a positive number, debounced function will be used to handle the resize event. Default: 0 # If specified a positive number, debounced function will be used to handle the resize event. Default: 0
debounce: Var[int] debounce: Var[int]
@ -242,8 +242,23 @@ class LabelList(Recharts):
stroke: Var[Union[str, Color]] = LiteralVar.create("none") stroke: Var[Union[str, Color]] = LiteralVar.create("none")
class Cell(Recharts):
"""A Cell component in Recharts."""
tag = "Cell"
alias = "RechartsCell"
# The presentation attribute of a rectangle in bar or a sector in pie.
fill: Var[str | Color]
# The presentation attribute of a rectangle in bar or a sector in pie.
stroke: Var[str | Color]
responsive_container = ResponsiveContainer.create responsive_container = ResponsiveContainer.create
legend = Legend.create legend = Legend.create
graphing_tooltip = GraphingTooltip.create graphing_tooltip = GraphingTooltip.create
label = Label.create label = Label.create
label_list = LabelList.create label_list = LabelList.create
cell = Cell.create

View File

@ -22,8 +22,8 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
aspect: Optional[Union[Var[int], int]] = None, aspect: Optional[Union[Var[int], int]] = None,
width: Optional[Union[Var[Union[int, str]], int, str]] = None, width: Optional[Union[Var[Union[int, str]], int, str]] = None,
height: Optional[Union[Var[Union[int, str]], int, str]] = None, height: Optional[Union[Var[Union[int, str]], int, str]] = None,
min_width: Optional[Union[Var[int], int]] = None, min_width: Optional[Union[Var[Union[int, str]], int, str]] = None,
min_height: Optional[Union[Var[int], int]] = None, min_height: Optional[Union[Var[Union[int, str]], int, str]] = None,
debounce: Optional[Union[Var[int], int]] = None, debounce: Optional[Union[Var[int], int]] = None,
style: Optional[Style] = None, style: Optional[Style] = None,
key: Optional[Any] = None, key: Optional[Any] = None,
@ -56,8 +56,8 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
aspect: The aspect ratio of the container. The final aspect ratio of the SVG element will be (width / height) * aspect. Number aspect: The aspect ratio of the container. The final aspect ratio of the SVG element will be (width / height) * aspect. Number
width: The width of chart container. Can be a number or string. Default: "100%" width: The width of chart container. Can be a number or string. Default: "100%"
height: The height of chart container. Can be a number or string. Default: "100%" height: The height of chart container. Can be a number or string. Default: "100%"
min_width: The minimum width of chart container. Number min_width: The minimum width of chart container. Number or string.
min_height: The minimum height of chart container. Number min_height: The minimum height of chart container. Number or string.
debounce: If specified a positive number, debounced function will be used to handle the resize event. Default: 0 debounce: If specified a positive number, debounced function will be used to handle the resize event. Default: 0
on_resize: If specified provides a callback providing the updated chart width and height values. on_resize: If specified provides a callback providing the updated chart width and height values.
style: The style of the component. style: The style of the component.
@ -482,8 +482,59 @@ class LabelList(Recharts):
""" """
... ...
class Cell(Recharts):
@overload
@classmethod
def create( # type: ignore
cls,
*children,
fill: Optional[Union[Color, Var[Union[Color, str]], str]] = None,
stroke: Optional[Union[Color, Var[Union[Color, str]], 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,
) -> "Cell":
"""Create the component.
Args:
*children: The children of the component.
fill: The presentation attribute of a rectangle in bar or a sector in pie.
stroke: The presentation attribute of a rectangle in bar or a sector in pie.
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.
"""
...
responsive_container = ResponsiveContainer.create responsive_container = ResponsiveContainer.create
legend = Legend.create legend = Legend.create
graphing_tooltip = GraphingTooltip.create graphing_tooltip = GraphingTooltip.create
label = Label.create label = Label.create
label_list = LabelList.create label_list = LabelList.create
cell = Cell.create

View File

@ -64,7 +64,7 @@ class Pie(Recharts):
legend_type: Var[LiteralLegendType] legend_type: Var[LiteralLegendType]
# If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally. Default: False # If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally. Default: False
label: Var[bool] = False # type: ignore label: Var[bool] = Var.create(False)
# If false set, label lines will not be drawn. If true set, label lines will be drawn which have the props calculated internally. Default: False # If false set, label lines will not be drawn. If true set, label lines will be drawn which have the props calculated internally. Default: False
label_line: Var[bool] label_line: Var[bool]
@ -73,7 +73,7 @@ class Pie(Recharts):
data: Var[List[Dict[str, Any]]] data: Var[List[Dict[str, Any]]]
# Valid children components # Valid children components
_valid_children: List[str] = ["Cell", "LabelList"] _valid_children: List[str] = ["Cell", "LabelList", "Bare"]
# Stoke color. Default: rx.color("accent", 9) # Stoke color. Default: rx.color("accent", 9)
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9)) stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))

View File

@ -1,6 +1,6 @@
"""A component that wraps a recharts lib.""" """A component that wraps a recharts lib."""
from typing import Dict, Literal from typing import Literal
from reflex.components.component import Component, MemoizationLeaf, NoSSRComponent from reflex.components.component import Component, MemoizationLeaf, NoSSRComponent
@ -8,16 +8,16 @@ from reflex.components.component import Component, MemoizationLeaf, NoSSRCompone
class Recharts(Component): class Recharts(Component):
"""A component that wraps a recharts lib.""" """A component that wraps a recharts lib."""
library = "recharts@2.13.0" library = "recharts@2.15.0"
def _get_style(self) -> Dict: def _get_style(self) -> dict:
return {"wrapperStyle": self.style} return {"wrapperStyle": self.style}
class RechartsCharts(NoSSRComponent, MemoizationLeaf): class RechartsCharts(NoSSRComponent, MemoizationLeaf):
"""A component that wraps a recharts lib.""" """A component that wraps a recharts lib."""
library = "recharts@2.13.0" library = "recharts@2.15.0"
LiteralAnimationEasing = Literal["ease", "ease-in", "ease-out", "ease-in-out", "linear"] LiteralAnimationEasing = Literal["ease", "ease-in", "ease-out", "ease-in-out", "linear"]

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