Merge remote-tracking branch 'upstream/main' into state-compression
This commit is contained in:
commit
9d40c25a9c
14
.github/workflows/benchmarks.yml
vendored
14
.github/workflows/benchmarks.yml
vendored
@ -81,15 +81,13 @@ jobs:
|
||||
matrix:
|
||||
# Show OS combos first in GUI
|
||||
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:
|
||||
- os: windows-latest
|
||||
python-version: "3.10.16"
|
||||
- os: windows-latest
|
||||
python-version: "3.9.21"
|
||||
python-version: "3.11.11"
|
||||
# keep only one python version for MacOS
|
||||
- os: macos-latest
|
||||
python-version: "3.9.21"
|
||||
- os: macos-latest
|
||||
python-version: "3.10.16"
|
||||
- os: macos-latest
|
||||
@ -98,7 +96,7 @@ jobs:
|
||||
- os: windows-latest
|
||||
python-version: "3.10.11"
|
||||
- os: windows-latest
|
||||
python-version: "3.9.13"
|
||||
python-version: "3.11.9"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@ -161,7 +159,11 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
|
@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/setup_build_env
|
||||
with:
|
||||
python-version: "3.9.21"
|
||||
python-version: '3.10'
|
||||
run-poetry-install: true
|
||||
create-venv-at-path: .venv
|
||||
|
||||
@ -55,7 +55,7 @@ jobs:
|
||||
path: reflex-web
|
||||
- name: Install Requirements for reflex-web
|
||||
working-directory: ./reflex-web
|
||||
run: poetry run uv pip install -r requirements.txt
|
||||
run: poetry run uv pip install $(grep -ivE "reflex " requirements.txt)
|
||||
- name: Install additional dependencies for DB access
|
||||
run: poetry run uv pip install psycopg
|
||||
- name: Init Website for reflex-web
|
||||
@ -73,7 +73,7 @@ jobs:
|
||||
echo "$outdated"
|
||||
|
||||
# Ignore 3rd party dependencies that are not updated.
|
||||
filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images' || true)
|
||||
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)
|
||||
|
||||
|
||||
|
11
.github/workflows/integration_app_harness.yml
vendored
11
.github/workflows/integration_app_harness.yml
vendored
@ -47,17 +47,10 @@ jobs:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
run-poetry-install: true
|
||||
create-venv-at-path: .venv
|
||||
- run: poetry run uv pip install pyvirtualdisplay pillow pytest-split
|
||||
- run: poetry run uv pip install pyvirtualdisplay pillow pytest-split pytest-retry
|
||||
- name: Run app harness tests
|
||||
env:
|
||||
SCREENSHOT_DIR: /tmp/screenshots/${{ matrix.state_manager }}/${{ matrix.python-version }}/${{ matrix.split_index }}
|
||||
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
|
||||
run: |
|
||||
poetry run playwright install chromium
|
||||
poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}}
|
||||
- uses: actions/upload-artifact@v4
|
||||
name: Upload failed test screenshots
|
||||
if: always()
|
||||
with:
|
||||
name: failed_test_screenshots
|
||||
path: /tmp/screenshots
|
||||
poetry run pytest tests/integration --retries 3 --maxfail=5 --splits 2 --group ${{matrix.split_index}}
|
||||
|
33
.github/workflows/integration_tests.yml
vendored
33
.github/workflows/integration_tests.yml
vendored
@ -33,7 +33,7 @@ env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
|
||||
jobs:
|
||||
example-counter:
|
||||
example-counter-and-nba-proxy:
|
||||
env:
|
||||
OUTPUT_FILE: import_benchmark.json
|
||||
timeout-minutes: 30
|
||||
@ -43,22 +43,17 @@ jobs:
|
||||
matrix:
|
||||
# Show OS combos first in GUI
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
|
||||
# Windows is a bit behind on Python version availability in Github
|
||||
python-version: ['3.10.16', '3.11.11', '3.12.8', '3.13.1']
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
python-version: "3.11.11"
|
||||
- os: windows-latest
|
||||
python-version: "3.10.16"
|
||||
- os: windows-latest
|
||||
python-version: "3.9.21"
|
||||
python-version: '3.10.16'
|
||||
include:
|
||||
- os: windows-latest
|
||||
python-version: "3.11.9"
|
||||
- os: windows-latest
|
||||
python-version: "3.10.11"
|
||||
- os: windows-latest
|
||||
python-version: "3.9.13"
|
||||
python-version: '3.10.11'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@ -119,6 +114,26 @@ jobs:
|
||||
--benchmark-json "./reflex-examples/counter/${{ env.OUTPUT_FILE }}"
|
||||
--branch-name "${{ github.head_ref || github.ref_name }}" --pr-id "${{ github.event.pull_request.id }}"
|
||||
--app-name "counter"
|
||||
- name: Install requirements for nba proxy example
|
||||
working-directory: ./reflex-examples/nba-proxy
|
||||
run: |
|
||||
poetry run uv pip install -r requirements.txt
|
||||
- name: Install additional dependencies for DB access
|
||||
run: poetry run uv pip install psycopg
|
||||
- name: Check export --backend-only before init for nba-proxy example
|
||||
working-directory: ./reflex-examples/nba-proxy
|
||||
run: |
|
||||
poetry run reflex export --backend-only
|
||||
- name: Init Website for nba-proxy example
|
||||
working-directory: ./reflex-examples/nba-proxy
|
||||
run: |
|
||||
poetry run reflex init --loglevel debug
|
||||
- name: Run Website and Check for errors
|
||||
run: |
|
||||
# Check that npm is home
|
||||
npm -v
|
||||
poetry run bash scripts/integration.sh ./reflex-examples/nba-proxy dev
|
||||
|
||||
|
||||
reflex-web:
|
||||
strategy:
|
||||
|
10
.github/workflows/unit_tests.yml
vendored
10
.github/workflows/unit_tests.yml
vendored
@ -28,22 +28,18 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
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:
|
||||
- os: windows-latest
|
||||
python-version: "3.11.11"
|
||||
- os: windows-latest
|
||||
python-version: "3.10.16"
|
||||
- os: windows-latest
|
||||
python-version: "3.9.21"
|
||||
include:
|
||||
- os: windows-latest
|
||||
python-version: "3.11.9"
|
||||
- os: windows-latest
|
||||
python-version: "3.10.11"
|
||||
- os: windows-latest
|
||||
python-version: "3.9.13"
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# Service containers to run with `runner-job`
|
||||
@ -92,8 +88,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Note: py39, 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"]
|
||||
# Note: py310, py311 versions chosen due to available arm64 darwin builds.
|
||||
python-version: ["3.10.11", "3.11.9", "3.12.8", "3.13.1"]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,6 +4,7 @@ assets/external/*
|
||||
dist/*
|
||||
examples/
|
||||
.web
|
||||
.states
|
||||
.idea
|
||||
.vscode
|
||||
.coverage
|
||||
|
@ -28,7 +28,7 @@ repos:
|
||||
entry: python3 scripts/make_pyi.py
|
||||
|
||||
- repo: https://github.com/RobertCraigie/pyright-python
|
||||
rev: v1.1.313
|
||||
rev: v1.1.392
|
||||
hooks:
|
||||
- id: pyright
|
||||
args: [reflex, tests]
|
||||
|
@ -8,7 +8,7 @@ Here is a quick guide on how to run Reflex repo locally so you can start contrib
|
||||
|
||||
**Prerequisites:**
|
||||
|
||||
- Python >= 3.9
|
||||
- Python >= 3.10
|
||||
- Poetry version >= 1.4.0 and add it to your path (see [Poetry Docs](https://python-poetry.org/docs/#installation) for more info).
|
||||
|
||||
**1. Fork this repository:**
|
||||
@ -87,7 +87,7 @@ poetry run ruff format .
|
||||
```
|
||||
|
||||
Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and `make_pyi` will run automatically before each commit.
|
||||
Note that pre-commit will only be installed when you use a Python version >= 3.9.
|
||||
Note that pre-commit will only be installed when you use a Python version >= 3.10.
|
||||
|
||||
``` bash
|
||||
pre-commit install
|
||||
|
@ -34,7 +34,7 @@ See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architectu
|
||||
|
||||
## ⚙️ Installation
|
||||
|
||||
Open a terminal and run (Requires Python 3.9+):
|
||||
Open a terminal and run (Requires Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -21,7 +21,7 @@ def get_package_size(venv_path: Path, os_name):
|
||||
ValueError: when venv does not exist or python version is None.
|
||||
"""
|
||||
python_version = get_python_version(venv_path, os_name)
|
||||
print("Python version:", python_version) # noqa: T201
|
||||
print("Python version:", python_version)
|
||||
if python_version is None:
|
||||
raise ValueError("Error: Failed to determine Python version.")
|
||||
|
||||
|
@ -34,13 +34,13 @@ def render_component(num: int):
|
||||
rx.box(
|
||||
rx.accordion.root(
|
||||
rx.accordion.item(
|
||||
header="Full Ingredients", # type: ignore
|
||||
content="Yes. It's built with accessibility in mind.", # type: ignore
|
||||
header="Full Ingredients",
|
||||
content="Yes. It's built with accessibility in mind.",
|
||||
font_size="3em",
|
||||
),
|
||||
rx.accordion.item(
|
||||
header="Applications", # type: ignore
|
||||
content="Yes. It's unstyled by default, giving you freedom over the look and feel.", # type: ignore
|
||||
header="Applications",
|
||||
content="Yes. It's unstyled by default, giving you freedom over the look and feel.",
|
||||
),
|
||||
collapsible=True,
|
||||
variant="ghost",
|
||||
@ -122,7 +122,7 @@ def AppWithTenComponentsOnePage():
|
||||
def index() -> rx.Component:
|
||||
return rx.center(rx.vstack(*render_component(1)))
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
app.add_page(index)
|
||||
|
||||
|
||||
@ -133,7 +133,7 @@ def AppWithHundredComponentOnePage():
|
||||
def index() -> rx.Component:
|
||||
return rx.center(rx.vstack(*render_component(100)))
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
app.add_page(index)
|
||||
|
||||
|
||||
@ -144,7 +144,7 @@ def AppWithThousandComponentsOnePage():
|
||||
def index() -> rx.Component:
|
||||
return rx.center(rx.vstack(*render_component(1000)))
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
app.add_page(index)
|
||||
|
||||
|
||||
@ -166,9 +166,9 @@ def app_with_10_components(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithTenComponentsOnePage,
|
||||
render_component=render_component, # type: ignore
|
||||
render_component=render_component, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
) # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@ -189,9 +189,9 @@ def app_with_100_components(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithHundredComponentOnePage,
|
||||
render_component=render_component, # type: ignore
|
||||
render_component=render_component, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
) # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@ -212,9 +212,9 @@ def app_with_1000_components(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithThousandComponentsOnePage,
|
||||
render_component=render_component, # type: ignore
|
||||
render_component=render_component, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
) # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
||||
|
@ -28,7 +28,7 @@ def render_multiple_pages(app, num: int):
|
||||
"""
|
||||
from typing import Tuple
|
||||
|
||||
from rxconfig import config # type: ignore
|
||||
from rxconfig import config # pyright: ignore [reportMissingImports]
|
||||
|
||||
import reflex as rx
|
||||
|
||||
@ -74,13 +74,13 @@ def render_multiple_pages(app, num: int):
|
||||
rx.select(
|
||||
["C", "PF", "SF", "PG", "SG"],
|
||||
placeholder="Select a position. (All)",
|
||||
on_change=State.set_position, # type: ignore
|
||||
on_change=State.set_position, # pyright: ignore [reportAttributeAccessIssue]
|
||||
size="3",
|
||||
),
|
||||
rx.select(
|
||||
college,
|
||||
placeholder="Select a college. (All)",
|
||||
on_change=State.set_college, # type: ignore
|
||||
on_change=State.set_college, # pyright: ignore [reportAttributeAccessIssue]
|
||||
size="3",
|
||||
),
|
||||
),
|
||||
@ -95,7 +95,7 @@ def render_multiple_pages(app, num: int):
|
||||
default_value=[18, 50],
|
||||
min=18,
|
||||
max=50,
|
||||
on_value_commit=State.set_age, # type: ignore
|
||||
on_value_commit=State.set_age, # pyright: ignore [reportAttributeAccessIssue]
|
||||
),
|
||||
align_items="left",
|
||||
width="100%",
|
||||
@ -110,7 +110,7 @@ def render_multiple_pages(app, num: int):
|
||||
default_value=[0, 25000000],
|
||||
min=0,
|
||||
max=25000000,
|
||||
on_value_commit=State.set_salary, # type: ignore
|
||||
on_value_commit=State.set_salary, # pyright: ignore [reportAttributeAccessIssue]
|
||||
),
|
||||
align_items="left",
|
||||
width="100%",
|
||||
@ -130,7 +130,7 @@ def render_multiple_pages(app, num: int):
|
||||
|
||||
def AppWithOnePage():
|
||||
"""A reflex app with one page."""
|
||||
from rxconfig import config # type: ignore
|
||||
from rxconfig import config # pyright: ignore [reportMissingImports]
|
||||
|
||||
import reflex as rx
|
||||
|
||||
@ -162,7 +162,7 @@ def AppWithOnePage():
|
||||
height="100vh",
|
||||
)
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
app.add_page(index)
|
||||
|
||||
|
||||
@ -170,7 +170,7 @@ def AppWithTenPages():
|
||||
"""A reflex app with 10 pages."""
|
||||
import reflex as rx
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
render_multiple_pages(app, 10)
|
||||
|
||||
|
||||
@ -178,7 +178,7 @@ def AppWithHundredPages():
|
||||
"""A reflex app with 100 pages."""
|
||||
import reflex as rx
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
render_multiple_pages(app, 100)
|
||||
|
||||
|
||||
@ -186,7 +186,7 @@ def AppWithThousandPages():
|
||||
"""A reflex app with Thousand pages."""
|
||||
import reflex as rx
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
render_multiple_pages(app, 1000)
|
||||
|
||||
|
||||
@ -194,7 +194,7 @@ def AppWithTenThousandPages():
|
||||
"""A reflex app with ten thousand pages."""
|
||||
import reflex as rx
|
||||
|
||||
app = rx.App(state=rx.State)
|
||||
app = rx.App(_state=rx.State)
|
||||
render_multiple_pages(app, 10000)
|
||||
|
||||
|
||||
@ -232,7 +232,7 @@ def app_with_ten_pages(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithTenPages,
|
||||
render_comp=render_multiple_pages, # type: ignore
|
||||
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
)
|
||||
|
||||
@ -255,9 +255,9 @@ def app_with_hundred_pages(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithHundredPages,
|
||||
render_comp=render_multiple_pages, # type: ignore
|
||||
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
) # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@ -278,9 +278,9 @@ def app_with_thousand_pages(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithThousandPages,
|
||||
render_comp=render_multiple_pages, # type: ignore
|
||||
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
) # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@ -301,9 +301,9 @@ def app_with_ten_thousand_pages(
|
||||
root=root,
|
||||
app_source=functools.partial(
|
||||
AppWithTenThousandPages,
|
||||
render_comp=render_multiple_pages, # type: ignore
|
||||
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||
),
|
||||
) # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
||||
|
@ -34,7 +34,7 @@ Auf unserer [Architektur-Seite](https://reflex.dev/blog/2024-03-21-reflex-archit
|
||||
|
||||
## ⚙️ Installation
|
||||
|
||||
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.9+):
|
||||
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -35,7 +35,7 @@ Consulta nuestra [página de arquitectura](https://reflex.dev/blog/2024-03-21-re
|
||||
|
||||
## ⚙️ Instalación
|
||||
|
||||
Abra un terminal y ejecute (Requiere Python 3.9+):
|
||||
Abra un terminal y ejecute (Requiere Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -35,7 +35,7 @@ Reflex के अंदर के कामकाज को जानने क
|
||||
|
||||
## ⚙️ इंस्टॉलेशन (Installation)
|
||||
|
||||
एक टर्मिनल खोलें और चलाएं (Python 3.9+ की आवश्यकता है):
|
||||
एक टर्मिनल खोलें और चलाएं (Python 3.10+ की आवश्यकता है):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
## ⚙️ Installazione
|
||||
|
||||
Apri un terminale ed esegui (Richiede Python 3.9+):
|
||||
Apri un terminale ed esegui (Richiede Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -37,7 +37,7 @@ Reflex がどのように動作しているかを知るには、[アーキテク
|
||||
|
||||
## ⚙️ インストール
|
||||
|
||||
ターミナルを開いて以下のコマンドを実行してください。(Python 3.9 以上が必要です。):
|
||||
ターミナルを開いて以下のコマンドを実行してください。(Python 3.10 以上が必要です。):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -20,7 +20,7 @@
|
||||
---
|
||||
## ⚙️ 설치
|
||||
|
||||
터미널을 열고 실행하세요. (Python 3.9+ 필요):
|
||||
터미널을 열고 실행하세요. (Python 3.10+ 필요):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -34,7 +34,7 @@
|
||||
|
||||
## ⚙️ Installation - نصب و راه اندازی
|
||||
|
||||
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.9+):
|
||||
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -21,7 +21,7 @@
|
||||
---
|
||||
## ⚙️ Instalação
|
||||
|
||||
Abra um terminal e execute (Requer Python 3.9+):
|
||||
Abra um terminal e execute (Requer Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
## ⚙️ Kurulum
|
||||
|
||||
Bir terminal açın ve çalıştırın (Python 3.9+ gerekir):
|
||||
Bir terminal açın ve çalıştırın (Python 3.10+ gerekir):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -34,7 +34,7 @@ Các tính năng chính:
|
||||
|
||||
## ⚙️ Cài đặt
|
||||
|
||||
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.9+):
|
||||
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -34,7 +34,7 @@ Reflex 是一个使用纯Python构建全栈web应用的库。
|
||||
|
||||
## ⚙️ 安装
|
||||
|
||||
打开一个终端并且运行(要求Python3.9+):
|
||||
打开一个终端并且运行(要求Python3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -36,7 +36,7 @@ Reflex 是一個可以用純 Python 構建全端網頁應用程式的函式庫
|
||||
|
||||
## ⚙️ 安裝
|
||||
|
||||
開啟一個終端機並且執行 (需要 Python 3.9+):
|
||||
開啟一個終端機並且執行 (需要 Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
1687
poetry.lock
generated
1687
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,7 @@ keywords = ["web", "framework"]
|
||||
classifiers = ["Development Status :: 4 - Beta"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
python = ">=3.10, <4.0"
|
||||
fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
|
||||
gunicorn = ">=20.1.0,<24.0"
|
||||
jinja2 = ">=3.1.2,<4.0"
|
||||
@ -50,13 +50,12 @@ httpx = ">=0.25.1,<1.0"
|
||||
twine = ">=4.0.0,<7.0"
|
||||
tomlkit = ">=0.12.4,<1.0"
|
||||
lazy_loader = ">=0.4"
|
||||
reflex-chakra = ">=0.6.0"
|
||||
typing_extensions = ">=4.6.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = ">=7.1.2,<9.0"
|
||||
pytest-mock = ">=3.10.0,<4.0"
|
||||
pyright = ">=1.1.229,<1.1.335"
|
||||
pyright = ">=1.1.392, <1.2"
|
||||
darglint = ">=1.8.1,<2.0"
|
||||
dill = ">=0.3.8"
|
||||
toml = ">=0.10.2,<1.0"
|
||||
@ -82,20 +81,24 @@ requires = ["poetry-core>=1.5.1"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.pyright]
|
||||
reportIncompatibleMethodOverride = false
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
output-format = "concise"
|
||||
lint.isort.split-on-trailing-comma = false
|
||||
lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "PERF", "PTH", "RUF", "SIM", "T", "W"]
|
||||
lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012"]
|
||||
lint.select = ["ANN001","B", "C4", "D", "E", "ERA", "F", "FURB", "I", "N", "PERF", "PGH", "PTH", "RUF", "SIM", "T", "TRY", "W"]
|
||||
lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012", "TRY0"]
|
||||
lint.pydocstyle.convention = "google"
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"__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"]
|
||||
"*.pyi" = ["D301", "D415", "D417", "D418", "E742"]
|
||||
"*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N", "PGH"]
|
||||
"pyi_generator.py" = ["N802"]
|
||||
"reflex/constants/*.py" = ["N"]
|
||||
"*/blank.py" = ["I001"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
@ -103,5 +106,5 @@ asyncio_default_fixture_loop_scope = "function"
|
||||
asyncio_mode = "auto"
|
||||
|
||||
[tool.codespell]
|
||||
skip = "docs/*,*.html,examples/*, *.pyi"
|
||||
skip = "docs/*,*.html,examples/*, *.pyi, poetry.lock"
|
||||
ignore-words-list = "te, TreeE"
|
||||
|
@ -8,7 +8,7 @@ version = "0.0.1"
|
||||
description = "Reflex custom component {{ module_name }}"
|
||||
readme = "README.md"
|
||||
license = { text = "Apache-2.0" }
|
||||
requires-python = ">=3.9"
|
||||
requires-python = ">=3.10"
|
||||
authors = [{ name = "", email = "YOUREMAIL@domain.com" }]
|
||||
keywords = ["reflex","reflex-custom-components"]
|
||||
|
||||
|
@ -86,11 +86,11 @@
|
||||
{% for condition in case[:-1] %}
|
||||
case JSON.stringify({{ condition._js_expr }}):
|
||||
{% endfor %}
|
||||
return {{ case[-1] }};
|
||||
return {{ render(case[-1]) }};
|
||||
break;
|
||||
{% endfor %}
|
||||
default:
|
||||
return {{ component.default }};
|
||||
return {{ render(component.default) }};
|
||||
break;
|
||||
}
|
||||
})()
|
||||
|
@ -16,10 +16,7 @@ export default function RadixThemesColorModeProvider({ children }) {
|
||||
if (isDevMode) {
|
||||
const lastCompiledTimeInLocalStorage =
|
||||
localStorage.getItem("last_compiled_time");
|
||||
if (
|
||||
lastCompiledTimeInLocalStorage &&
|
||||
lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp
|
||||
) {
|
||||
if (lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp) {
|
||||
// on app startup, make sure the application color mode is persisted correctly.
|
||||
setTheme(defaultColorMode);
|
||||
localStorage.setItem("last_compiled_time", lastCompiledTimeStamp);
|
||||
|
@ -3,6 +3,7 @@ import axios from "axios";
|
||||
import io from "socket.io-client";
|
||||
import JSON5 from "json5";
|
||||
import env from "$/env.json";
|
||||
import reflexEnvironment from "$/reflex.json";
|
||||
import Cookies from "universal-cookie";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Router, { useRouter } from "next/router";
|
||||
@ -407,6 +408,7 @@ export const connect = async (
|
||||
socket.current = io(endpoint.href, {
|
||||
path: endpoint["pathname"],
|
||||
transports: transports,
|
||||
protocols: [reflexEnvironment.version],
|
||||
autoUnref: false,
|
||||
});
|
||||
// Ensure undefined fields in events are sent as null instead of removed
|
||||
|
@ -84,6 +84,9 @@ In the example above, you will be able to do `rx.list`
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
|
||||
from reflex.utils import (
|
||||
compat, # for side-effects
|
||||
lazy_loader,
|
||||
@ -303,7 +306,6 @@ _MAPPING: dict = {
|
||||
"event": [
|
||||
"EventChain",
|
||||
"EventHandler",
|
||||
"background",
|
||||
"call_script",
|
||||
"call_function",
|
||||
"run_script",
|
||||
@ -366,20 +368,5 @@ getattr, __dir__, __all__ = lazy_loader.attach(
|
||||
)
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "chakra":
|
||||
from reflex.utils import console
|
||||
|
||||
console.deprecate(
|
||||
"rx.chakra",
|
||||
reason="and moved to a separate package. "
|
||||
"To continue using Chakra UI components, install the `reflex-chakra` package via `pip install "
|
||||
"reflex-chakra`.",
|
||||
deprecation_version="0.6.0",
|
||||
removal_version="0.7.0",
|
||||
dedupe=True,
|
||||
)
|
||||
import reflex_chakra as rc
|
||||
|
||||
return rc
|
||||
def __getattr__(name: ModuleType | Any):
|
||||
return getattr(name)
|
||||
|
@ -131,7 +131,7 @@ from .components.radix.themes.layout.container import container as container
|
||||
from .components.radix.themes.layout.flex import flex as flex
|
||||
from .components.radix.themes.layout.grid import grid as grid
|
||||
from .components.radix.themes.layout.list import list_item as list_item
|
||||
from .components.radix.themes.layout.list import list_ns as list # noqa
|
||||
from .components.radix.themes.layout.list import list_ns as list # noqa: F401
|
||||
from .components.radix.themes.layout.list import ordered_list as ordered_list
|
||||
from .components.radix.themes.layout.list import unordered_list as unordered_list
|
||||
from .components.radix.themes.layout.section import section as section
|
||||
@ -156,7 +156,6 @@ from .constants import Env as Env
|
||||
from .constants.colors import Color as Color
|
||||
from .event import EventChain as EventChain
|
||||
from .event import EventHandler as EventHandler
|
||||
from .event import background as background
|
||||
from .event import call_function as call_function
|
||||
from .event import call_script as call_script
|
||||
from .event import clear_local_storage as clear_local_storage
|
||||
|
274
reflex/app.py
274
reflex/app.py
@ -27,6 +27,7 @@ from typing import (
|
||||
Dict,
|
||||
Generic,
|
||||
List,
|
||||
MutableMapping,
|
||||
Optional,
|
||||
Set,
|
||||
Type,
|
||||
@ -145,7 +146,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
|
||||
position="top-center",
|
||||
id="backend_error",
|
||||
style={"width": "500px"},
|
||||
) # type: ignore
|
||||
) # pyright: ignore [reportReturnType]
|
||||
else:
|
||||
error_message.insert(0, "An error occurred.")
|
||||
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.
|
||||
html_custom_attrs: Optional[Dict[str, str]] = None
|
||||
|
||||
# A map from a route to an unevaluated page. PRIVATE.
|
||||
unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
|
||||
# A map from a route to an unevaluated page.
|
||||
_unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
# A map from a page route to the component to render. Users should use `add_page`. PRIVATE.
|
||||
pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
|
||||
# A map from a page route to the component to render. Users should use `add_page`.
|
||||
_pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
|
||||
|
||||
# The backend API object. PRIVATE.
|
||||
api: FastAPI = None # type: ignore
|
||||
# The backend API object.
|
||||
_api: FastAPI | None = None
|
||||
|
||||
# The state class to use for the app. PRIVATE.
|
||||
state: Optional[Type[BaseState]] = None
|
||||
# The state class to use for the app.
|
||||
_state: Optional[Type[BaseState]] = None
|
||||
|
||||
# Class to manage many client states.
|
||||
_state_manager: Optional[StateManager] = None
|
||||
|
||||
# Mapping from a route to event handlers to trigger when the page loads. PRIVATE.
|
||||
load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
|
||||
# Mapping from a route to event handlers to trigger when the page loads.
|
||||
_load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
# Admin dashboard to view and manage the database. PRIVATE.
|
||||
# Admin dashboard to view and manage the database.
|
||||
admin_dash: Optional[AdminDash] = None
|
||||
|
||||
# The async server name space. PRIVATE.
|
||||
event_namespace: Optional[EventNamespace] = None
|
||||
# The async server name space.
|
||||
_event_namespace: Optional[EventNamespace] = None
|
||||
|
||||
# Background tasks that are currently running. PRIVATE.
|
||||
background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
|
||||
# Background tasks that are currently running.
|
||||
_background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
|
||||
|
||||
# Frontend Error Handler Function
|
||||
frontend_exception_handler: Callable[[Exception], None] = (
|
||||
@ -292,6 +293,24 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
[Exception], Union[EventSpec, List[EventSpec], None]
|
||||
] = default_backend_exception_handler
|
||||
|
||||
@property
|
||||
def api(self) -> FastAPI | None:
|
||||
"""Get the backend api.
|
||||
|
||||
Returns:
|
||||
The backend api.
|
||||
"""
|
||||
return self._api
|
||||
|
||||
@property
|
||||
def event_namespace(self) -> EventNamespace | None:
|
||||
"""Get the event namespace.
|
||||
|
||||
Returns:
|
||||
The event namespace.
|
||||
"""
|
||||
return self._event_namespace
|
||||
|
||||
def __post_init__(self):
|
||||
"""Initialize the app.
|
||||
|
||||
@ -311,7 +330,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
set_breakpoints(self.style.pop("breakpoints"))
|
||||
|
||||
# Set up the API.
|
||||
self.api = FastAPI(lifespan=self._run_lifespan_tasks)
|
||||
self._api = FastAPI(lifespan=self._run_lifespan_tasks)
|
||||
self._add_cors()
|
||||
self._add_default_endpoints()
|
||||
|
||||
@ -334,8 +353,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
def _enable_state(self) -> None:
|
||||
"""Enable state for the app."""
|
||||
if not self.state:
|
||||
self.state = State
|
||||
if not self._state:
|
||||
self._state = State
|
||||
self._setup_state()
|
||||
|
||||
def _setup_state(self) -> None:
|
||||
@ -344,13 +363,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
Raises:
|
||||
RuntimeError: If the socket server is invalid.
|
||||
"""
|
||||
if not self.state:
|
||||
if not self._state:
|
||||
return
|
||||
|
||||
config = get_config()
|
||||
|
||||
# Set up the state manager.
|
||||
self._state_manager = StateManager.create(state=self.state)
|
||||
self._state_manager = StateManager.create(state=self._state)
|
||||
|
||||
# Set up the Socket.IO AsyncServer.
|
||||
if not self.sio:
|
||||
@ -381,12 +400,42 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
namespace = config.get_event_namespace()
|
||||
|
||||
# Create the event namespace and attach the main app. Not related to any paths.
|
||||
self.event_namespace = EventNamespace(namespace, self)
|
||||
self._event_namespace = EventNamespace(namespace, self)
|
||||
|
||||
# Register the event namespace with the socket.
|
||||
self.sio.register_namespace(self.event_namespace)
|
||||
# Mount the socket app with the API.
|
||||
self.api.mount(str(constants.Endpoint.EVENT), socket_app)
|
||||
if self.api:
|
||||
|
||||
class HeaderMiddleware:
|
||||
def __init__(self, app: ASGIApp):
|
||||
self.app = app
|
||||
|
||||
async def __call__(
|
||||
self, scope: MutableMapping[str, Any], receive: Any, send: Callable
|
||||
):
|
||||
original_send = send
|
||||
|
||||
async def modified_send(message: dict):
|
||||
if message["type"] == "websocket.accept":
|
||||
if scope.get("subprotocols"):
|
||||
# The following *does* say "subprotocol" instead of "subprotocols", intentionally.
|
||||
message["subprotocol"] = scope["subprotocols"][0]
|
||||
|
||||
headers = dict(message.get("headers", []))
|
||||
header_key = b"sec-websocket-protocol"
|
||||
if subprotocol := headers.get(header_key):
|
||||
message["headers"] = [
|
||||
*message.get("headers", []),
|
||||
(header_key, subprotocol),
|
||||
]
|
||||
|
||||
return await original_send(message)
|
||||
|
||||
return await self.app(scope, receive, modified_send)
|
||||
|
||||
socket_app_with_headers = HeaderMiddleware(socket_app)
|
||||
self.api.mount(str(constants.Endpoint.EVENT), socket_app_with_headers)
|
||||
|
||||
# Check the exception handlers
|
||||
self._validate_exception_handlers()
|
||||
@ -397,24 +446,35 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
Returns:
|
||||
The string representation of the app.
|
||||
"""
|
||||
return f"<App state={self.state.__name__ if self.state else None}>"
|
||||
return f"<App state={self._state.__name__ if self._state else None}>"
|
||||
|
||||
def __call__(self) -> FastAPI:
|
||||
"""Run the backend api instance.
|
||||
|
||||
Raises:
|
||||
ValueError: If the app has not been initialized.
|
||||
|
||||
Returns:
|
||||
The backend api.
|
||||
"""
|
||||
if not self.api:
|
||||
raise ValueError("The app has not been initialized.")
|
||||
return self.api
|
||||
|
||||
def _add_default_endpoints(self):
|
||||
"""Add default api endpoints (ping)."""
|
||||
# To test the server.
|
||||
if not self.api:
|
||||
return
|
||||
|
||||
self.api.get(str(constants.Endpoint.PING))(ping)
|
||||
self.api.get(str(constants.Endpoint.HEALTH))(health)
|
||||
|
||||
def _add_optional_endpoints(self):
|
||||
"""Add optional api endpoints (_upload)."""
|
||||
if not self.api:
|
||||
return
|
||||
|
||||
if Upload.is_used:
|
||||
# To upload files.
|
||||
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
||||
@ -432,6 +492,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
def _add_cors(self):
|
||||
"""Add CORS middleware to the app."""
|
||||
if not self.api:
|
||||
return
|
||||
self.api.add_middleware(
|
||||
cors.CORSMiddleware,
|
||||
allow_credentials=True,
|
||||
@ -463,14 +525,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
Returns:
|
||||
The generated component.
|
||||
|
||||
Raises:
|
||||
exceptions.MatchTypeError: If the return types of match cases in rx.match are different.
|
||||
"""
|
||||
try:
|
||||
return component if isinstance(component, Component) else component()
|
||||
except exceptions.MatchTypeError:
|
||||
raise
|
||||
return component if isinstance(component, Component) else component()
|
||||
|
||||
def add_page(
|
||||
self,
|
||||
@ -527,13 +583,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
# Check if the route given is valid
|
||||
verify_route_validity(route)
|
||||
|
||||
if route in self.unevaluated_pages and environment.RELOAD_CONFIG.is_set():
|
||||
if route in self._unevaluated_pages and environment.RELOAD_CONFIG.is_set():
|
||||
# when the app is reloaded(typically for app harness tests), we should maintain
|
||||
# the latest render function of a route.This applies typically to decorated pages
|
||||
# since they are only added when app._compile is called.
|
||||
self.unevaluated_pages.pop(route)
|
||||
self._unevaluated_pages.pop(route)
|
||||
|
||||
if route in self.unevaluated_pages:
|
||||
if route in self._unevaluated_pages:
|
||||
route_name = (
|
||||
f"`{route}` or `/`"
|
||||
if route == constants.PageNames.INDEX_ROUTE
|
||||
@ -546,15 +602,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
# Setup dynamic args for the route.
|
||||
# this state assignment is only required for tests using the deprecated state kwarg for App
|
||||
state = self.state if self.state else State
|
||||
state = self._state if self._state else State
|
||||
state.setup_dynamic_args(get_route_args(route))
|
||||
|
||||
if on_load:
|
||||
self.load_events[route] = (
|
||||
self._load_events[route] = (
|
||||
on_load if isinstance(on_load, list) else [on_load]
|
||||
)
|
||||
|
||||
self.unevaluated_pages[route] = UnevaluatedPage(
|
||||
self._unevaluated_pages[route] = UnevaluatedPage(
|
||||
component=component,
|
||||
route=route,
|
||||
title=title,
|
||||
@ -564,14 +620,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
def _compile_page(self, route: str):
|
||||
def _compile_page(self, route: str, save_page: bool = True):
|
||||
"""Compile a page.
|
||||
|
||||
Args:
|
||||
route: The route of the page to compile.
|
||||
save_page: If True, the compiled page is saved to self._pages.
|
||||
"""
|
||||
component, enable_state = compiler.compile_unevaluated_page(
|
||||
route, self.unevaluated_pages[route], self.state, self.style, self.theme
|
||||
route, self._unevaluated_pages[route], self._state, self.style, self.theme
|
||||
)
|
||||
|
||||
if enable_state:
|
||||
@ -579,7 +636,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
# Add the page.
|
||||
self._check_routes_conflict(route)
|
||||
self.pages[route] = component
|
||||
if save_page:
|
||||
self._pages[route] = component
|
||||
|
||||
def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
|
||||
"""Get the load events for a route.
|
||||
@ -593,7 +651,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
route = route.lstrip("/")
|
||||
if route == "":
|
||||
route = constants.PageNames.INDEX_ROUTE
|
||||
return self.load_events.get(route, [])
|
||||
return self._load_events.get(route, [])
|
||||
|
||||
def _check_routes_conflict(self, new_route: str):
|
||||
"""Verify if there is any conflict between the new route and any existing route.
|
||||
@ -617,10 +675,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
|
||||
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
|
||||
)
|
||||
for route in self.pages:
|
||||
for route in self._pages:
|
||||
replaced_route = replace_brackets_with_keywords(route)
|
||||
for rw, r, nr in zip(
|
||||
replaced_route.split("/"), route.split("/"), new_route.split("/")
|
||||
replaced_route.split("/"),
|
||||
route.split("/"),
|
||||
new_route.split("/"),
|
||||
strict=False,
|
||||
):
|
||||
if rw in segments and r != nr:
|
||||
# If the slugs in the segments of both routes are not the same, then the route is invalid
|
||||
@ -651,8 +712,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
Args:
|
||||
component: The component to display at the page.
|
||||
title: The title of the page.
|
||||
description: The description of the page.
|
||||
image: The image to display on the page.
|
||||
description: The description of the page.
|
||||
on_load: The event handler(s) that will be called each time the page load.
|
||||
meta: The metadata of the page.
|
||||
"""
|
||||
@ -675,6 +736,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
def _setup_admin_dash(self):
|
||||
"""Setup the admin dash."""
|
||||
# Get the admin dash.
|
||||
if not self.api:
|
||||
return
|
||||
|
||||
admin_dash = self.admin_dash
|
||||
|
||||
if admin_dash and admin_dash.models:
|
||||
@ -716,7 +780,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
frontend_packages = get_config().frontend_packages
|
||||
_frontend_packages = []
|
||||
for package in frontend_packages:
|
||||
if package in (get_config().tailwind or {}).get("plugins", []): # type: ignore
|
||||
if package in (get_config().tailwind or {}).get("plugins", []):
|
||||
console.warn(
|
||||
f"Tailwind packages are inferred from 'plugins', remove `{package}` from `frontend_packages`"
|
||||
)
|
||||
@ -779,10 +843,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
def _setup_overlay_component(self):
|
||||
"""If a State is not used and no overlay_component is specified, do not render the connection modal."""
|
||||
if self.state is None and self.overlay_component is default_overlay_component:
|
||||
if self._state is None and self.overlay_component is default_overlay_component:
|
||||
self.overlay_component = None
|
||||
for k, component in self.pages.items():
|
||||
self.pages[k] = self._add_overlay_to_component(component)
|
||||
for k, component in self._pages.items():
|
||||
self._pages[k] = self._add_overlay_to_component(component)
|
||||
|
||||
def _add_error_boundary_to_component(self, component: Component) -> Component:
|
||||
if self.error_boundary is None:
|
||||
@ -794,14 +858,14 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
def _setup_error_boundary(self):
|
||||
"""If a State is not used and no error_boundary is specified, do not render the error boundary."""
|
||||
if self.state is None and self.error_boundary is default_error_boundary:
|
||||
if self._state is None and self.error_boundary is default_error_boundary:
|
||||
self.error_boundary = None
|
||||
|
||||
for k, component in self.pages.items():
|
||||
for k, component in self._pages.items():
|
||||
# Skip the 404 page
|
||||
if k == constants.Page404.SLUG:
|
||||
continue
|
||||
self.pages[k] = self._add_error_boundary_to_component(component)
|
||||
self._pages[k] = self._add_error_boundary_to_component(component)
|
||||
|
||||
def _apply_decorated_pages(self):
|
||||
"""Add @rx.page decorated pages to the app.
|
||||
@ -827,11 +891,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
Raises:
|
||||
VarDependencyError: When a computed var has an invalid dependency.
|
||||
"""
|
||||
if not self.state:
|
||||
if not self._state:
|
||||
return
|
||||
|
||||
if not state:
|
||||
state = self.state
|
||||
state = self._state
|
||||
|
||||
for var in state.computed_vars.values():
|
||||
if not var._cache:
|
||||
@ -857,13 +921,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
"""
|
||||
from reflex.utils.exceptions import ReflexRuntimeError
|
||||
|
||||
self.pages = {}
|
||||
self._pages = {}
|
||||
|
||||
def get_compilation_time() -> str:
|
||||
return str(datetime.now().time()).split(".")[0]
|
||||
|
||||
# Render a default 404 page if the user didn't supply one
|
||||
if constants.Page404.SLUG not in self.unevaluated_pages:
|
||||
if constants.Page404.SLUG not in self._unevaluated_pages:
|
||||
self.add_page(route=constants.Page404.SLUG)
|
||||
|
||||
# Fix up the style.
|
||||
@ -879,14 +943,16 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
# If a theme component was provided, wrap the app with it
|
||||
app_wrappers[(20, "Theme")] = self.theme
|
||||
|
||||
for route in self.unevaluated_pages:
|
||||
should_compile = self._should_compile()
|
||||
|
||||
for route in self._unevaluated_pages:
|
||||
console.debug(f"Evaluating page: {route}")
|
||||
self._compile_page(route)
|
||||
self._compile_page(route, save_page=should_compile)
|
||||
|
||||
# Add the optional endpoints (_upload)
|
||||
self._add_optional_endpoints()
|
||||
|
||||
if not self._should_compile():
|
||||
if not should_compile:
|
||||
return
|
||||
|
||||
self._validate_var_dependencies()
|
||||
@ -906,7 +972,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
progress.start()
|
||||
task = progress.add_task(
|
||||
f"[{get_compilation_time()}] Compiling:",
|
||||
total=len(self.pages)
|
||||
total=len(self._pages)
|
||||
+ fixed_pages_within_executor
|
||||
+ adhoc_steps_without_executor,
|
||||
)
|
||||
@ -925,7 +991,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
# This has to happen before compiling stateful components as that
|
||||
# prevents recursive functions from reaching all components.
|
||||
for component in self.pages.values():
|
||||
for component in self._pages.values():
|
||||
# Add component._get_all_imports() to all_imports.
|
||||
all_imports.update(component._get_all_imports())
|
||||
|
||||
@ -940,12 +1006,12 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
stateful_components_path,
|
||||
stateful_components_code,
|
||||
page_components,
|
||||
) = compiler.compile_stateful_components(self.pages.values())
|
||||
) = compiler.compile_stateful_components(self._pages.values())
|
||||
|
||||
progress.advance(task)
|
||||
|
||||
# Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
|
||||
if code_uses_state_contexts(stateful_components_code) and self.state is None:
|
||||
if code_uses_state_contexts(stateful_components_code) and self._state is None:
|
||||
raise ReflexRuntimeError(
|
||||
"To access rx.State in frontend components, at least one "
|
||||
"subclass of rx.State must be defined in the app."
|
||||
@ -959,7 +1025,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
compiler.compile_document_root(
|
||||
self.head_components,
|
||||
html_lang=self.html_lang,
|
||||
html_custom_attrs=self.html_custom_attrs, # type: ignore
|
||||
html_custom_attrs=self.html_custom_attrs, # pyright: ignore [reportArgumentType]
|
||||
)
|
||||
)
|
||||
|
||||
@ -982,20 +1048,20 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
|
||||
)
|
||||
|
||||
for route, component in zip(self.pages, page_components):
|
||||
for route, component in zip(self._pages, page_components, strict=True):
|
||||
ExecutorSafeFunctions.COMPONENTS[route] = component
|
||||
|
||||
ExecutorSafeFunctions.STATE = self.state
|
||||
ExecutorSafeFunctions.STATE = self._state
|
||||
|
||||
with executor:
|
||||
result_futures = []
|
||||
|
||||
def _submit_work(fn, *args, **kwargs):
|
||||
def _submit_work(fn: Callable, *args, **kwargs):
|
||||
f = executor.submit(fn, *args, **kwargs)
|
||||
result_futures.append(f)
|
||||
|
||||
# Compile the pre-compiled pages.
|
||||
for route in self.pages:
|
||||
for route in self._pages:
|
||||
_submit_work(
|
||||
ExecutorSafeFunctions.compile_page,
|
||||
route,
|
||||
@ -1030,7 +1096,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
# Compile the contexts.
|
||||
compile_results.append(
|
||||
compiler.compile_contexts(self.state, self.theme),
|
||||
compiler.compile_contexts(self._state, self.theme),
|
||||
)
|
||||
if self.theme is not None:
|
||||
# Fix #2992 by removing the top-level appearance prop
|
||||
@ -1152,9 +1218,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
)
|
||||
|
||||
task = asyncio.create_task(_coro())
|
||||
self.background_tasks.add(task)
|
||||
self._background_tasks.add(task)
|
||||
# Clean up task from background_tasks set when complete.
|
||||
task.add_done_callback(self.background_tasks.discard)
|
||||
task.add_done_callback(self._background_tasks.discard)
|
||||
return task
|
||||
|
||||
def _validate_exception_handlers(self):
|
||||
@ -1164,11 +1230,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
ValueError: If the custom exception handlers are invalid.
|
||||
|
||||
"""
|
||||
FRONTEND_ARG_SPEC = {
|
||||
frontend_arg_spec = {
|
||||
"exception": Exception,
|
||||
}
|
||||
|
||||
BACKEND_ARG_SPEC = {
|
||||
backend_arg_spec = {
|
||||
"exception": Exception,
|
||||
}
|
||||
|
||||
@ -1176,9 +1242,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
["frontend", "backend"],
|
||||
[self.frontend_exception_handler, self.backend_exception_handler],
|
||||
[
|
||||
FRONTEND_ARG_SPEC,
|
||||
BACKEND_ARG_SPEC,
|
||||
frontend_arg_spec,
|
||||
backend_arg_spec,
|
||||
],
|
||||
strict=True,
|
||||
):
|
||||
if hasattr(handler_fn, "__name__"):
|
||||
_fn_name = handler_fn.__name__
|
||||
@ -1320,15 +1387,14 @@ async def process(
|
||||
if app._process_background(state, event) is not None:
|
||||
# `final=True` allows the frontend send more events immediately.
|
||||
yield StateUpdate(final=True)
|
||||
return
|
||||
else:
|
||||
# Process the event synchronously.
|
||||
async for update in state._process(event):
|
||||
# Postprocess the event.
|
||||
update = await app._postprocess(state, event, update)
|
||||
|
||||
# Process the event synchronously.
|
||||
async for update in state._process(event):
|
||||
# Postprocess the event.
|
||||
update = await app._postprocess(state, event, update)
|
||||
|
||||
# Yield the update.
|
||||
yield update
|
||||
# Yield the update.
|
||||
yield update
|
||||
except Exception as ex:
|
||||
telemetry.send_error(ex, context="backend")
|
||||
|
||||
@ -1523,16 +1589,20 @@ class EventNamespace(AsyncNamespace):
|
||||
self.sid_to_token = {}
|
||||
self.app = app
|
||||
|
||||
def on_connect(self, sid, environ):
|
||||
def on_connect(self, sid: str, environ: dict):
|
||||
"""Event for when the websocket is connected.
|
||||
|
||||
Args:
|
||||
sid: The Socket.IO session id.
|
||||
environ: The request information, including HTTP headers.
|
||||
"""
|
||||
pass
|
||||
subprotocol = environ.get("HTTP_SEC_WEBSOCKET_PROTOCOL")
|
||||
if subprotocol and subprotocol != constants.Reflex.VERSION:
|
||||
console.warn(
|
||||
f"Frontend version {subprotocol} for session {sid} does not match the backend version {constants.Reflex.VERSION}."
|
||||
)
|
||||
|
||||
def on_disconnect(self, sid):
|
||||
def on_disconnect(self, sid: str):
|
||||
"""Event for when the websocket disconnects.
|
||||
|
||||
Args:
|
||||
@ -1554,7 +1624,7 @@ class EventNamespace(AsyncNamespace):
|
||||
self.emit(str(constants.SocketEvent.EVENT), update, to=sid)
|
||||
)
|
||||
|
||||
async def on_event(self, sid, data):
|
||||
async def on_event(self, sid: str, data: Any):
|
||||
"""Event for receiving front-end websocket events.
|
||||
|
||||
Raises:
|
||||
@ -1563,10 +1633,36 @@ class EventNamespace(AsyncNamespace):
|
||||
Args:
|
||||
sid: The Socket.IO session id.
|
||||
data: The event data.
|
||||
|
||||
Raises:
|
||||
EventDeserializationError: If the event data is not a dictionary.
|
||||
"""
|
||||
fields = 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.sid_to_token[sid] = event.token
|
||||
@ -1595,7 +1691,7 @@ class EventNamespace(AsyncNamespace):
|
||||
# Emit the update from processing the event.
|
||||
await self.emit_update(update=update, sid=sid)
|
||||
|
||||
async def on_ping(self, sid):
|
||||
async def on_ping(self, sid: str):
|
||||
"""Event for testing the API endpoint.
|
||||
|
||||
Args:
|
||||
|
@ -12,7 +12,7 @@ from typing import Callable, Coroutine, Set, Union
|
||||
from fastapi import FastAPI
|
||||
|
||||
from reflex.utils import console
|
||||
from reflex.utils.exceptions import InvalidLifespanTaskType
|
||||
from reflex.utils.exceptions import InvalidLifespanTaskTypeError
|
||||
|
||||
from .mixin import AppMixin
|
||||
|
||||
@ -32,7 +32,7 @@ class LifespanMixin(AppMixin):
|
||||
try:
|
||||
async with contextlib.AsyncExitStack() as stack:
|
||||
for task in self.lifespan_tasks:
|
||||
run_msg = f"Started lifespan task: {task.__name__} as {{type}}" # type: ignore
|
||||
run_msg = f"Started lifespan task: {task.__name__} as {{type}}" # pyright: ignore [reportAttributeAccessIssue]
|
||||
if isinstance(task, asyncio.Task):
|
||||
running_tasks.append(task)
|
||||
else:
|
||||
@ -61,19 +61,19 @@ class LifespanMixin(AppMixin):
|
||||
|
||||
Args:
|
||||
task: The task to register.
|
||||
task_kwargs: The kwargs of the task.
|
||||
**task_kwargs: The kwargs of the task.
|
||||
|
||||
Raises:
|
||||
InvalidLifespanTaskType: If the task is a generator function.
|
||||
InvalidLifespanTaskTypeError: If the task is a generator function.
|
||||
"""
|
||||
if inspect.isgeneratorfunction(task) or inspect.isasyncgenfunction(task):
|
||||
raise InvalidLifespanTaskType(
|
||||
raise InvalidLifespanTaskTypeError(
|
||||
f"Task {task.__name__} of type generator must be decorated with contextlib.asynccontextmanager."
|
||||
)
|
||||
|
||||
if task_kwargs:
|
||||
original_task = task
|
||||
task = functools.partial(task, **task_kwargs) # type: ignore
|
||||
functools.update_wrapper(task, original_task) # type: ignore
|
||||
self.lifespan_tasks.add(task) # type: ignore
|
||||
console.debug(f"Registered lifespan task: {task.__name__}") # type: ignore
|
||||
task = functools.partial(task, **task_kwargs) # pyright: ignore [reportArgumentType]
|
||||
functools.update_wrapper(task, original_task) # pyright: ignore [reportArgumentType]
|
||||
self.lifespan_tasks.add(task)
|
||||
console.debug(f"Registered lifespan task: {task.__name__}") # pyright: ignore [reportAttributeAccessIssue]
|
||||
|
@ -53,11 +53,11 @@ class MiddlewareMixin(AppMixin):
|
||||
"""
|
||||
for middleware in self.middleware:
|
||||
if asyncio.iscoroutinefunction(middleware.preprocess):
|
||||
out = await middleware.preprocess(app=self, state=state, event=event) # type: ignore
|
||||
out = await middleware.preprocess(app=self, state=state, event=event) # pyright: ignore [reportArgumentType]
|
||||
else:
|
||||
out = middleware.preprocess(app=self, state=state, event=event) # type: ignore
|
||||
out = middleware.preprocess(app=self, state=state, event=event) # pyright: ignore [reportArgumentType]
|
||||
if out is not None:
|
||||
return out # type: ignore
|
||||
return out # pyright: ignore [reportReturnType]
|
||||
|
||||
async def _postprocess(
|
||||
self, state: BaseState, event: Event, update: StateUpdate
|
||||
@ -78,18 +78,18 @@ class MiddlewareMixin(AppMixin):
|
||||
for middleware in self.middleware:
|
||||
if asyncio.iscoroutinefunction(middleware.postprocess):
|
||||
out = await middleware.postprocess(
|
||||
app=self, # type: ignore
|
||||
app=self, # pyright: ignore [reportArgumentType]
|
||||
state=state,
|
||||
event=event,
|
||||
update=update,
|
||||
)
|
||||
else:
|
||||
out = middleware.postprocess(
|
||||
app=self, # type: ignore
|
||||
app=self, # pyright: ignore [reportArgumentType]
|
||||
state=state,
|
||||
event=event,
|
||||
update=update,
|
||||
)
|
||||
if out is not None:
|
||||
return out # type: ignore
|
||||
return out # pyright: ignore [reportReturnType]
|
||||
return update
|
||||
|
@ -5,16 +5,13 @@ Only the app attribute is explicitly exposed.
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from reflex import constants
|
||||
from reflex.utils import telemetry
|
||||
from reflex.utils.exec import is_prod_mode
|
||||
from reflex.utils.prerequisites import get_app
|
||||
from reflex.utils.prerequisites import get_and_validate_app
|
||||
|
||||
if constants.CompileVars.APP != "app":
|
||||
raise AssertionError("unexpected variable name for 'app'")
|
||||
|
||||
telemetry.send("compile")
|
||||
app_module = get_app(reload=False)
|
||||
app = getattr(app_module, constants.CompileVars.APP)
|
||||
app, app_module = get_and_validate_app(reload=False)
|
||||
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
|
||||
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
||||
app._apply_decorated_pages()
|
||||
@ -30,8 +27,7 @@ if is_prod_mode():
|
||||
# ensure only "app" is exposed.
|
||||
del app_module
|
||||
del compile_future
|
||||
del get_app
|
||||
del get_and_validate_app
|
||||
del is_prod_mode
|
||||
del telemetry
|
||||
del constants
|
||||
del ThreadPoolExecutor
|
||||
|
@ -13,7 +13,7 @@ except ModuleNotFoundError:
|
||||
if not TYPE_CHECKING:
|
||||
import pydantic.main as pydantic_main
|
||||
from pydantic import BaseModel
|
||||
from pydantic.fields import ModelField # type: ignore
|
||||
from pydantic.fields import ModelField
|
||||
|
||||
|
||||
def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None:
|
||||
@ -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
|
||||
# shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True)
|
||||
pydantic_main.validate_field_name = validate_field_name # type: ignore
|
||||
pydantic_main.validate_field_name = validate_field_name # pyright: ignore [reportPossiblyUnboundVariable, reportPrivateImportUsage]
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from reflex.vars import Var
|
||||
|
||||
|
||||
class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
||||
class Base(BaseModel): # pyright: ignore [reportPossiblyUnboundVariable]
|
||||
"""The base class subclassed by all Reflex classes.
|
||||
|
||||
This class wraps Pydantic and provides common methods such as
|
||||
@ -75,12 +75,12 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
||||
"""
|
||||
from reflex.utils.serializers import serialize
|
||||
|
||||
return self.__config__.json_dumps( # type: ignore
|
||||
return self.__config__.json_dumps(
|
||||
self.dict(),
|
||||
default=serialize,
|
||||
)
|
||||
|
||||
def set(self, **kwargs):
|
||||
def set(self, **kwargs: Any):
|
||||
"""Set multiple fields and return the object.
|
||||
|
||||
Args:
|
||||
@ -113,12 +113,12 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
||||
default_value: The default value of the field
|
||||
"""
|
||||
var_name = var._var_field_name
|
||||
new_field = ModelField.infer(
|
||||
new_field = ModelField.infer( # pyright: ignore [reportPossiblyUnboundVariable]
|
||||
name=var_name,
|
||||
value=default_value,
|
||||
annotation=var._var_type,
|
||||
class_validators=None,
|
||||
config=cls.__config__, # type: ignore
|
||||
config=cls.__config__,
|
||||
)
|
||||
cls.__fields__.update({var_name: new_field})
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, Optional, Type, Union
|
||||
from urllib.parse import urlparse
|
||||
@ -12,7 +14,9 @@ from reflex.vars.base import Var
|
||||
try:
|
||||
from pydantic.v1.fields import ModelField
|
||||
except ModuleNotFoundError:
|
||||
from pydantic.fields import ModelField # type: ignore
|
||||
from pydantic.fields import (
|
||||
ModelField, # pyright: ignore [reportAttributeAccessIssue]
|
||||
)
|
||||
|
||||
from reflex import constants
|
||||
from reflex.components.base import (
|
||||
@ -115,7 +119,7 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]:
|
||||
default, rest = compile_import_statement(fields)
|
||||
|
||||
# prevent lib from being rendered on the page if all imports are non rendered kind
|
||||
if not any(f.render for f in fields): # type: ignore
|
||||
if not any(f.render for f in fields):
|
||||
continue
|
||||
|
||||
if not lib:
|
||||
@ -163,8 +167,12 @@ def compile_state(state: Type[BaseState]) -> dict:
|
||||
try:
|
||||
initial_state = state(_reflex_internal_init=True).dict(initial=True)
|
||||
except Exception as e:
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d__%H-%M-%S")
|
||||
constants.Reflex.LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
log_path = constants.Reflex.LOGS_DIR / f"state_compile_error_{timestamp}.log"
|
||||
traceback.TracebackException.from_exception(e).print(file=log_path.open("w+"))
|
||||
console.warn(
|
||||
f"Failed to compile initial state with computed vars, excluding them: {e}"
|
||||
f"Failed to compile initial state with computed vars. Error log saved to {log_path}"
|
||||
)
|
||||
initial_state = state(_reflex_internal_init=True).dict(
|
||||
initial=True, include_computed=False
|
||||
@ -494,7 +502,7 @@ def empty_dir(path: str | Path, keep_files: list[str] | None = None):
|
||||
path_ops.rm(element)
|
||||
|
||||
|
||||
def is_valid_url(url) -> bool:
|
||||
def is_valid_url(url: str) -> bool:
|
||||
"""Check if a url is valid.
|
||||
|
||||
Args:
|
||||
|
@ -31,7 +31,7 @@ class Bare(Component):
|
||||
return cls(contents=contents)
|
||||
else:
|
||||
contents = str(contents) if contents is not None else ""
|
||||
return cls(contents=contents) # type: ignore
|
||||
return cls(contents=contents)
|
||||
|
||||
def _get_all_hooks_internal(self) -> dict[str, VarData | None]:
|
||||
"""Include the hooks for the component.
|
||||
|
@ -53,11 +53,11 @@ class Description(Meta):
|
||||
"""A component that displays the title of the current page."""
|
||||
|
||||
# The type of the description.
|
||||
name: str = "description"
|
||||
name: str | None = "description"
|
||||
|
||||
|
||||
class Image(Meta):
|
||||
"""A component that displays the title of the current page."""
|
||||
|
||||
# The type of the image.
|
||||
property: str = "og:image"
|
||||
property: str | None = "og:image"
|
||||
|
@ -23,8 +23,6 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from typing_extensions import deprecated
|
||||
|
||||
import reflex.state
|
||||
from reflex.base import Base
|
||||
from reflex.compiler.templates import STATEFUL_COMPONENT
|
||||
@ -47,11 +45,10 @@ from reflex.event import (
|
||||
EventChain,
|
||||
EventHandler,
|
||||
EventSpec,
|
||||
EventVar,
|
||||
no_args_event_spec,
|
||||
)
|
||||
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 (
|
||||
ImmutableParsedImportDict,
|
||||
ImportDict,
|
||||
@ -153,7 +150,7 @@ class BaseComponent(Base, ABC):
|
||||
class ComponentNamespace(SimpleNamespace):
|
||||
"""A namespace to manage components with subcomponents."""
|
||||
|
||||
def __hash__(self) -> int:
|
||||
def __hash__(self) -> int: # pyright: ignore [reportIncompatibleVariableOverride]
|
||||
"""Get the hash of the namespace.
|
||||
|
||||
Returns:
|
||||
@ -429,20 +426,22 @@ class Component(BaseComponent, ABC):
|
||||
else:
|
||||
continue
|
||||
|
||||
def determine_key(value: Any):
|
||||
# Try to create a var from the value
|
||||
key = value if isinstance(value, Var) else LiteralVar.create(value)
|
||||
|
||||
# Check that the var type is not None.
|
||||
if key is None:
|
||||
raise TypeError
|
||||
|
||||
return key
|
||||
|
||||
# Check whether the key is a component prop.
|
||||
if types._issubclass(field_type, Var):
|
||||
# Used to store the passed types if var type is a union.
|
||||
passed_types = None
|
||||
try:
|
||||
# Try to create a var from the value.
|
||||
if isinstance(value, Var):
|
||||
kwargs[key] = value
|
||||
else:
|
||||
kwargs[key] = LiteralVar.create(value)
|
||||
|
||||
# Check that the var type is not None.
|
||||
if kwargs[key] is None:
|
||||
raise TypeError
|
||||
kwargs[key] = determine_key(value)
|
||||
|
||||
expected_type = fields[key].outer_type_.__args__[0]
|
||||
# validate literal fields.
|
||||
@ -463,9 +462,7 @@ class Component(BaseComponent, ABC):
|
||||
if types.is_union(passed_type):
|
||||
# We need to check all possible types in the union.
|
||||
passed_types = (
|
||||
arg
|
||||
for arg in passed_type.__args__ # type: ignore
|
||||
if arg is not type(None)
|
||||
arg for arg in passed_type.__args__ if arg is not type(None)
|
||||
)
|
||||
if (
|
||||
# If the passed var is a union, check if all possible types are valid.
|
||||
@ -492,7 +489,7 @@ class Component(BaseComponent, ABC):
|
||||
# Check if the key is an event trigger.
|
||||
if key in component_specific_triggers:
|
||||
kwargs["event_triggers"][key] = EventChain.create(
|
||||
value=value, # type: ignore
|
||||
value=value,
|
||||
args_spec=component_specific_triggers[key],
|
||||
key=key,
|
||||
)
|
||||
@ -545,41 +542,6 @@ class Component(BaseComponent, ABC):
|
||||
# Construct the component.
|
||||
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(
|
||||
self,
|
||||
) -> Dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]]:
|
||||
@ -614,7 +576,7 @@ class Component(BaseComponent, ABC):
|
||||
annotation = field.annotation
|
||||
if (metadata := getattr(annotation, "__metadata__", None)) is not None:
|
||||
args_spec = metadata[0]
|
||||
default_triggers[field.name] = args_spec or (no_args_event_spec) # type: ignore
|
||||
default_triggers[field.name] = args_spec or (no_args_event_spec)
|
||||
return default_triggers
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@ -740,22 +702,21 @@ class Component(BaseComponent, ABC):
|
||||
# Import here to avoid circular imports.
|
||||
from reflex.components.base.bare import Bare
|
||||
from reflex.components.base.fragment import Fragment
|
||||
from reflex.utils.exceptions import ComponentTypeError
|
||||
from reflex.utils.exceptions import ChildrenTypeError
|
||||
|
||||
# Filter out None props
|
||||
props = {key: value for key, value in props.items() if value is not None}
|
||||
|
||||
def validate_children(children):
|
||||
def validate_children(children: tuple | list):
|
||||
for child in children:
|
||||
if isinstance(child, tuple):
|
||||
if isinstance(child, (tuple, list)):
|
||||
validate_children(child)
|
||||
|
||||
# Make sure the child is a valid type.
|
||||
if not types._isinstance(child, ComponentChild):
|
||||
raise ComponentTypeError(
|
||||
"Children of Reflex components must be other components, "
|
||||
"state vars, or primitive Python types. "
|
||||
f"Got child {child} of type {type(child)}.",
|
||||
)
|
||||
if isinstance(child, dict) or not types._isinstance(
|
||||
child, ComponentChild
|
||||
):
|
||||
raise ChildrenTypeError(component=cls.__name__, child=child)
|
||||
|
||||
# Validate all the children.
|
||||
validate_children(children)
|
||||
@ -798,7 +759,7 @@ class Component(BaseComponent, ABC):
|
||||
|
||||
# Walk the MRO to call all `add_style` methods.
|
||||
for base in self._iter_parent_classes_with_method("add_style"):
|
||||
s = base.add_style(self) # type: ignore
|
||||
s = base.add_style(self)
|
||||
if s is not None:
|
||||
styles.append(s)
|
||||
|
||||
@ -890,7 +851,7 @@ class Component(BaseComponent, ABC):
|
||||
else {}
|
||||
)
|
||||
|
||||
def render(self) -> Dict:
|
||||
def render(self) -> dict:
|
||||
"""Render the component.
|
||||
|
||||
Returns:
|
||||
@ -908,7 +869,7 @@ class Component(BaseComponent, ABC):
|
||||
self._replace_prop_names(rendered_dict)
|
||||
return rendered_dict
|
||||
|
||||
def _replace_prop_names(self, rendered_dict) -> None:
|
||||
def _replace_prop_names(self, rendered_dict: dict) -> None:
|
||||
"""Replace the prop names in the render dictionary.
|
||||
|
||||
Args:
|
||||
@ -948,7 +909,7 @@ class Component(BaseComponent, ABC):
|
||||
comp.__name__ for comp in (Fragment, Foreach, Cond, Match)
|
||||
]
|
||||
|
||||
def validate_child(child):
|
||||
def validate_child(child: Any):
|
||||
child_name = type(child).__name__
|
||||
|
||||
# Iterate through the immediate children of fragment
|
||||
@ -1711,7 +1672,7 @@ class CustomComponent(Component):
|
||||
if base_value is not None and isinstance(value, Component):
|
||||
self.component_props[key] = value
|
||||
value = base_value._replace(
|
||||
merge_var_data=VarData( # type: ignore
|
||||
merge_var_data=VarData(
|
||||
imports=value._get_all_imports(),
|
||||
hooks=value._get_all_hooks(),
|
||||
)
|
||||
@ -1744,7 +1705,7 @@ class CustomComponent(Component):
|
||||
return hash(self.tag)
|
||||
|
||||
@classmethod
|
||||
def get_props(cls) -> Set[str]:
|
||||
def get_props(cls) -> Set[str]: # pyright: ignore [reportIncompatibleVariableOverride]
|
||||
"""Get the props for the component.
|
||||
|
||||
Returns:
|
||||
@ -1839,7 +1800,7 @@ class CustomComponent(Component):
|
||||
include_children=include_children, ignore_ids=ignore_ids
|
||||
)
|
||||
|
||||
@lru_cache(maxsize=None) # noqa
|
||||
@lru_cache(maxsize=None) # noqa: B019
|
||||
def get_component(self) -> Component:
|
||||
"""Render the component.
|
||||
|
||||
@ -2428,7 +2389,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
|
||||
if tag["name"] == "match":
|
||||
element = tag["cond"]
|
||||
|
||||
conditionals = tag["default"]
|
||||
conditionals = render_dict_to_var(tag["default"], imported_names)
|
||||
|
||||
for case in tag["match_cases"][::-1]:
|
||||
condition = case[0].to_string() == element.to_string()
|
||||
@ -2437,7 +2398,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
|
||||
|
||||
conditionals = ternary_operation(
|
||||
condition,
|
||||
case[-1],
|
||||
render_dict_to_var(case[-1], imported_names),
|
||||
conditionals,
|
||||
)
|
||||
|
||||
|
@ -25,7 +25,7 @@ from reflex.vars.function import FunctionStringVar
|
||||
from reflex.vars.number import BooleanVar
|
||||
from reflex.vars.sequence import LiteralArrayVar
|
||||
|
||||
connect_error_var_data: VarData = VarData( # type: ignore
|
||||
connect_error_var_data: VarData = VarData(
|
||||
imports=Imports.EVENTS,
|
||||
hooks={Hooks.EVENTS: None},
|
||||
)
|
||||
@ -99,14 +99,14 @@ class ConnectionToaster(Toaster):
|
||||
"""
|
||||
toast_id = "websocket-error"
|
||||
target_url = WebsocketTargetURL.create()
|
||||
props = ToastProps( # type: ignore
|
||||
props = ToastProps(
|
||||
description=LiteralVar.create(
|
||||
f"Check if server is reachable at {target_url}",
|
||||
),
|
||||
close_button=True,
|
||||
duration=120000,
|
||||
id=toast_id,
|
||||
)
|
||||
) # pyright: ignore [reportCallIssue]
|
||||
|
||||
individual_hooks = [
|
||||
f"const toast_props = {LiteralVar.create(props)!s};",
|
||||
@ -116,7 +116,7 @@ class ConnectionToaster(Toaster):
|
||||
_var_data=VarData(
|
||||
imports={
|
||||
"react": ["useEffect", "useState"],
|
||||
**dict(target_url._get_all_var_data().imports), # type: ignore
|
||||
**dict(target_url._get_all_var_data().imports), # pyright: ignore [reportArgumentType, reportOptionalMemberAccess]
|
||||
}
|
||||
),
|
||||
).call(
|
||||
|
@ -82,7 +82,9 @@ class Breakpoints(Dict[K, V]):
|
||||
return Breakpoints(
|
||||
{
|
||||
k: v
|
||||
for k, v in zip(["initial", *breakpoint_names], thresholds)
|
||||
for k, v in zip(
|
||||
["initial", *breakpoint_names], thresholds, strict=True
|
||||
)
|
||||
if v is not None
|
||||
}
|
||||
)
|
||||
|
@ -41,7 +41,7 @@ class ClientSideRouting(Component):
|
||||
return ""
|
||||
|
||||
|
||||
def wait_for_client_redirect(component) -> Component:
|
||||
def wait_for_client_redirect(component: Component) -> Component:
|
||||
"""Wait for a redirect to occur before rendering a component.
|
||||
|
||||
This prevents the 404 page from flashing while the redirect is happening.
|
||||
|
@ -60,7 +60,7 @@ class ClientSideRouting(Component):
|
||||
"""
|
||||
...
|
||||
|
||||
def wait_for_client_redirect(component) -> Component: ...
|
||||
def wait_for_client_redirect(component: Component) -> Component: ...
|
||||
|
||||
class Default404Page(Component):
|
||||
@overload
|
||||
|
@ -26,10 +26,9 @@ class Cond(MemoizationLeaf):
|
||||
cond: Var[Any]
|
||||
|
||||
# The component to render if the cond is true.
|
||||
comp1: BaseComponent = None # type: ignore
|
||||
|
||||
comp1: BaseComponent | None = None
|
||||
# The component to render if the cond is false.
|
||||
comp2: BaseComponent = None # type: ignore
|
||||
comp2: BaseComponent | None = None
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
@ -73,8 +72,8 @@ class Cond(MemoizationLeaf):
|
||||
def _render(self) -> Tag:
|
||||
return CondTag(
|
||||
cond=self.cond,
|
||||
true_value=self.comp1.render(),
|
||||
false_value=self.comp2.render(),
|
||||
true_value=self.comp1.render(), # pyright: ignore [reportOptionalMemberAccess]
|
||||
false_value=self.comp2.render(), # pyright: ignore [reportOptionalMemberAccess]
|
||||
)
|
||||
|
||||
def render(self) -> Dict:
|
||||
@ -111,7 +110,7 @@ class Cond(MemoizationLeaf):
|
||||
|
||||
|
||||
@overload
|
||||
def cond(condition: Any, c1: Component, c2: Any) -> Component: ...
|
||||
def cond(condition: Any, c1: Component, c2: Any) -> Component: ... # pyright: ignore [reportOverlappingOverload]
|
||||
|
||||
|
||||
@overload
|
||||
@ -154,7 +153,7 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
|
||||
if c2 is None:
|
||||
raise ValueError("For conditional vars, the second argument must be set.")
|
||||
|
||||
def create_var(cond_part):
|
||||
def create_var(cond_part: Any) -> Var[Any]:
|
||||
return LiteralVar.create(cond_part)
|
||||
|
||||
# convert the truth and false cond parts into vars so the _var_data can be obtained.
|
||||
@ -163,16 +162,16 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
|
||||
|
||||
# Create the conditional var.
|
||||
return ternary_operation(
|
||||
cond_var.bool()._replace( # type: ignore
|
||||
cond_var.bool()._replace(
|
||||
merge_var_data=VarData(imports=_IS_TRUE_IMPORT),
|
||||
), # type: ignore
|
||||
),
|
||||
c1,
|
||||
c2,
|
||||
)
|
||||
|
||||
|
||||
@overload
|
||||
def color_mode_cond(light: Component, dark: Component | None = None) -> Component: ... # type: ignore
|
||||
def color_mode_cond(light: Component, dark: Component | None = None) -> Component: ... # pyright: ignore [reportOverlappingOverload]
|
||||
|
||||
|
||||
@overload
|
||||
|
@ -28,7 +28,7 @@ class DebounceInput(Component):
|
||||
min_length: Var[int]
|
||||
|
||||
# Time to wait between end of input and triggering on_change
|
||||
debounce_timeout: Var[int] = DEFAULT_DEBOUNCE_TIMEOUT # type: ignore
|
||||
debounce_timeout: Var[int] = Var.create(DEFAULT_DEBOUNCE_TIMEOUT)
|
||||
|
||||
# If true, notify when Enter key is pressed
|
||||
force_notify_by_enter: Var[bool]
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
from typing import Any, Callable, Iterable
|
||||
|
||||
@ -97,9 +98,20 @@ class Foreach(Component):
|
||||
# Determine the index var name based on the params accepted by render_fn.
|
||||
props["index_var_name"] = params[1].name
|
||||
else:
|
||||
render_fn = self.render_fn
|
||||
# Otherwise, use a deterministic index, based on the render function bytecode.
|
||||
code_hash = (
|
||||
hash(self.render_fn.__code__)
|
||||
hash(
|
||||
getattr(
|
||||
render_fn,
|
||||
"__code__",
|
||||
(
|
||||
repr(self.render_fn)
|
||||
if not isinstance(render_fn, functools.partial)
|
||||
else render_fn.func.__code__
|
||||
),
|
||||
)
|
||||
)
|
||||
.to_bytes(
|
||||
length=8,
|
||||
byteorder="big",
|
||||
|
@ -14,7 +14,7 @@ class Html(Div):
|
||||
"""
|
||||
|
||||
# The HTML to render.
|
||||
dangerouslySetInnerHTML: Var[Dict[str, str]]
|
||||
dangerouslySetInnerHTML: Var[Dict[str, str]] # noqa: N815
|
||||
|
||||
@classmethod
|
||||
def create(cls, *children, **props):
|
||||
|
@ -109,7 +109,7 @@ class Match(MemoizationLeaf):
|
||||
return cases, default
|
||||
|
||||
@classmethod
|
||||
def _create_case_var_with_var_data(cls, case_element):
|
||||
def _create_case_var_with_var_data(cls, case_element: Any) -> Var:
|
||||
"""Convert a case element into a Var.If the case
|
||||
is a Style type, we extract the var data and merge it with the
|
||||
newly created Var.
|
||||
@ -222,7 +222,7 @@ class Match(MemoizationLeaf):
|
||||
cond=match_cond_var,
|
||||
match_cases=match_cases,
|
||||
default=default,
|
||||
children=[case[-1] for case in match_cases] + [default], # type: ignore
|
||||
children=[case[-1] for case in match_cases] + [default], # pyright: ignore [reportArgumentType]
|
||||
)
|
||||
)
|
||||
|
||||
@ -236,13 +236,13 @@ class Match(MemoizationLeaf):
|
||||
_js_expr=format.format_match(
|
||||
cond=str(match_cond_var),
|
||||
match_cases=match_cases,
|
||||
default=default, # type: ignore
|
||||
default=default, # pyright: ignore [reportArgumentType]
|
||||
),
|
||||
_var_type=default._var_type, # type: ignore
|
||||
_var_type=default._var_type, # pyright: ignore [reportAttributeAccessIssue,reportOptionalMemberAccess]
|
||||
_var_data=VarData.merge(
|
||||
match_cond_var._get_all_var_data(),
|
||||
*[el._get_all_var_data() for case in match_cases for el in case],
|
||||
default._get_all_var_data(), # type: ignore
|
||||
default._get_all_var_data(), # pyright: ignore [reportAttributeAccessIssue, reportOptionalMemberAccess]
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -192,7 +192,7 @@ class GhostUpload(Fragment):
|
||||
class Upload(MemoizationLeaf):
|
||||
"""A file upload component."""
|
||||
|
||||
library = "react-dropzone@14.2.10"
|
||||
library = "react-dropzone@14.3.5"
|
||||
|
||||
tag = ""
|
||||
|
||||
@ -269,7 +269,7 @@ class Upload(MemoizationLeaf):
|
||||
on_drop = upload_props["on_drop"]
|
||||
if isinstance(on_drop, Callable):
|
||||
# Call the lambda to get the event chain.
|
||||
on_drop = call_event_fn(on_drop, _on_drop_spec) # type: ignore
|
||||
on_drop = call_event_fn(on_drop, _on_drop_spec)
|
||||
if isinstance(on_drop, EventSpec):
|
||||
# Update the provided args for direct use with on_drop.
|
||||
on_drop = on_drop.with_args(
|
||||
|
@ -14,7 +14,7 @@ from reflex.components.radix.themes.layout.box import Box
|
||||
from reflex.constants.colors import Color
|
||||
from reflex.event import set_clipboard
|
||||
from reflex.style import Style
|
||||
from reflex.utils import console, format
|
||||
from reflex.utils import format
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.vars.base import LiteralVar, Var, VarData
|
||||
|
||||
@ -382,7 +382,7 @@ for theme_name in dir(Theme):
|
||||
class CodeBlock(Component, MarkdownComponentMap):
|
||||
"""A code block."""
|
||||
|
||||
library = "react-syntax-highlighter@15.6.0"
|
||||
library = "react-syntax-highlighter@15.6.1"
|
||||
|
||||
tag = "PrismAsyncLight"
|
||||
|
||||
@ -438,6 +438,8 @@ class CodeBlock(Component, MarkdownComponentMap):
|
||||
can_copy = props.pop("can_copy", False)
|
||||
copy_button = props.pop("copy_button", None)
|
||||
|
||||
# react-syntax-highlighter doesn't have an explicit "light" or "dark" theme so we use one-light and one-dark
|
||||
# themes respectively to ensure code compatibility.
|
||||
if "theme" not in props:
|
||||
# Default color scheme responds to global color mode.
|
||||
props["theme"] = color_mode_cond(
|
||||
@ -445,20 +447,9 @@ class CodeBlock(Component, MarkdownComponentMap):
|
||||
dark=Theme.one_dark,
|
||||
)
|
||||
|
||||
# react-syntax-highlighter 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:
|
||||
code = children[0]
|
||||
copy_button = ( # type: ignore
|
||||
copy_button = (
|
||||
copy_button
|
||||
if copy_button is not None
|
||||
else Button.create(
|
||||
|
@ -165,7 +165,7 @@ class DataEditor(NoSSRComponent):
|
||||
|
||||
tag = "DataEditor"
|
||||
is_default = True
|
||||
library: str = "@glideapps/glide-data-grid@^6.0.3"
|
||||
library: str | None = "@glideapps/glide-data-grid@^6.0.3"
|
||||
lib_dependencies: List[str] = [
|
||||
"lodash@^4.17.21",
|
||||
"react-responsive-carousel@^3.2.7",
|
||||
@ -321,6 +321,8 @@ class DataEditor(NoSSRComponent):
|
||||
Returns:
|
||||
The import dict.
|
||||
"""
|
||||
if self.library is None:
|
||||
return {}
|
||||
return {
|
||||
"": f"{format.format_library_name(self.library)}/dist/index.css",
|
||||
self.library: "GridCellKind",
|
||||
@ -343,7 +345,7 @@ class DataEditor(NoSSRComponent):
|
||||
data_callback = self.get_cell_content._js_expr
|
||||
else:
|
||||
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])" "{"]
|
||||
|
||||
|
@ -15,10 +15,8 @@ def svg_logo(color: Union[str, rx.Var[str]] = rx.color_mode_cond("#110F1F", "whi
|
||||
The Reflex logo SVG.
|
||||
"""
|
||||
|
||||
def logo_path(d):
|
||||
return rx.el.svg.path(
|
||||
d=d,
|
||||
)
|
||||
def logo_path(d: str):
|
||||
return rx.el.svg.path(d=d)
|
||||
|
||||
paths = [
|
||||
"M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z",
|
||||
|
@ -602,7 +602,7 @@ class ShikiCodeBlock(Component, MarkdownComponentMap):
|
||||
|
||||
transformer_styles = {}
|
||||
# Collect styles from transformers and wrapper
|
||||
for transformer in code_block.transformers._var_value: # type: ignore
|
||||
for transformer in code_block.transformers._var_value: # pyright: ignore [reportAttributeAccessIssue]
|
||||
if isinstance(transformer, ShikiBaseTransformers) and transformer.style:
|
||||
transformer_styles.update(transformer.style)
|
||||
transformer_styles.update(code_wrapper_props.pop("style", {}))
|
||||
@ -653,8 +653,9 @@ class ShikiCodeBlock(Component, MarkdownComponentMap):
|
||||
raise ValueError(
|
||||
f"the function names should be str names of functions in the specified transformer: {library!r}"
|
||||
)
|
||||
return ShikiBaseTransformers( # type: ignore
|
||||
library=library, fns=[FunctionStringVar.create(fn) for fn in fns]
|
||||
return ShikiBaseTransformers(
|
||||
library=library,
|
||||
fns=[FunctionStringVar.create(fn) for fn in fns], # pyright: ignore [reportCallIssue]
|
||||
)
|
||||
|
||||
def _render(self, props: dict[str, Any] | None = None):
|
||||
@ -757,13 +758,13 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
|
||||
|
||||
if can_copy:
|
||||
code = children[0]
|
||||
copy_button = ( # type: ignore
|
||||
copy_button = (
|
||||
copy_button
|
||||
if copy_button is not None
|
||||
else Button.create(
|
||||
Icon.create(tag="copy", size=16, color=color("gray", 11)),
|
||||
on_click=[
|
||||
set_clipboard(cls._strip_transformer_triggers(code)), # type: ignore
|
||||
set_clipboard(cls._strip_transformer_triggers(code)),
|
||||
copy_script(),
|
||||
],
|
||||
style=Style(
|
||||
|
@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Union
|
||||
|
||||
from reflex import constants
|
||||
from reflex.utils import imports
|
||||
from reflex.utils.exceptions import DynamicComponentMissingLibrary
|
||||
from reflex.utils.exceptions import DynamicComponentMissingLibraryError
|
||||
from reflex.utils.format import format_library_name
|
||||
from reflex.utils.serializers import serializer
|
||||
from reflex.vars import Var, get_unique_variable_name
|
||||
@ -36,13 +36,15 @@ def bundle_library(component: Union["Component", str]):
|
||||
component: The component to bundle the library with.
|
||||
|
||||
Raises:
|
||||
DynamicComponentMissingLibrary: Raised when a dynamic component is missing a library.
|
||||
DynamicComponentMissingLibraryError: Raised when a dynamic component is missing a library.
|
||||
"""
|
||||
if isinstance(component, str):
|
||||
bundled_libraries.add(component)
|
||||
return
|
||||
if component.library is None:
|
||||
raise DynamicComponentMissingLibrary("Component must have a library to bundle.")
|
||||
raise DynamicComponentMissingLibraryError(
|
||||
"Component must have a library to bundle."
|
||||
)
|
||||
bundled_libraries.add(format_library_name(component.library))
|
||||
|
||||
|
||||
|
@ -48,4 +48,4 @@ PROP_TO_ELEMENTS = {
|
||||
ELEMENT_TO_PROPS = defaultdict(list)
|
||||
for prop, elements in PROP_TO_ELEMENTS.items():
|
||||
for el in elements:
|
||||
ELEMENT_TO_PROPS[el].append(prop) # type: ignore
|
||||
ELEMENT_TO_PROPS[el].append(prop)
|
||||
|
@ -6,7 +6,7 @@ from reflex.components.component import Component
|
||||
class Element(Component):
|
||||
"""The base class for all raw HTML elements."""
|
||||
|
||||
def __eq__(self, other):
|
||||
def __eq__(self, other: object):
|
||||
"""Two elements are equal if they have the same tag.
|
||||
|
||||
Args:
|
||||
|
@ -102,7 +102,7 @@ class Fieldset(Element):
|
||||
name: Var[Union[str, int, bool]]
|
||||
|
||||
|
||||
def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
|
||||
def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]:
|
||||
"""Event handler spec for the on_submit event.
|
||||
|
||||
Returns:
|
||||
@ -111,7 +111,7 @@ def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
|
||||
return (FORM_DATA,)
|
||||
|
||||
|
||||
def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]:
|
||||
def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]:
|
||||
"""Event handler spec for the on_submit event.
|
||||
|
||||
Returns:
|
||||
@ -153,7 +153,7 @@ class Form(BaseHTML):
|
||||
target: Var[Union[str, int, bool]]
|
||||
|
||||
# If true, the form will be cleared after submit.
|
||||
reset_on_submit: Var[bool] = False # type: ignore
|
||||
reset_on_submit: Var[bool] = Var.create(False)
|
||||
|
||||
# The name used to make this form's submit handler function unique.
|
||||
handle_submit_unique_name: Var[str]
|
||||
@ -405,7 +405,7 @@ class Input(BaseHTML):
|
||||
(value_var := Var.create(value))._var_type
|
||||
):
|
||||
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.create(""),
|
||||
|
@ -270,8 +270,8 @@ class Fieldset(Element):
|
||||
"""
|
||||
...
|
||||
|
||||
def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]: ...
|
||||
def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]: ...
|
||||
def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]: ...
|
||||
def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]: ...
|
||||
|
||||
class Form(BaseHTML):
|
||||
@overload
|
||||
@ -341,10 +341,10 @@ class Form(BaseHTML):
|
||||
on_submit: Optional[
|
||||
Union[
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||
],
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||
],
|
||||
]
|
||||
] = None,
|
||||
|
@ -2,13 +2,15 @@
|
||||
|
||||
from reflex.components.component import Component
|
||||
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):
|
||||
"""Lucide Icon Component."""
|
||||
|
||||
library = "lucide-react@0.469.0"
|
||||
library = "lucide-react@0.471.1"
|
||||
|
||||
|
||||
class Icon(LucideIconComponent):
|
||||
@ -32,6 +34,7 @@ class Icon(LucideIconComponent):
|
||||
Raises:
|
||||
AttributeError: The errors tied to bad usage of the Icon component.
|
||||
ValueError: If the icon tag is invalid.
|
||||
TypeError: If the icon name is not a string.
|
||||
|
||||
Returns:
|
||||
The created component.
|
||||
@ -39,7 +42,6 @@ class Icon(LucideIconComponent):
|
||||
if children:
|
||||
if len(children) == 1 and isinstance(children[0], str):
|
||||
props["tag"] = children[0]
|
||||
children = []
|
||||
else:
|
||||
raise AttributeError(
|
||||
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:
|
||||
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 (
|
||||
not isinstance(props["tag"], str)
|
||||
or format.to_snake_case(props["tag"]) not in LUCIDE_ICON_LIST
|
||||
not isinstance(tag, str)
|
||||
or format.to_snake_case(tag) not in LUCIDE_ICON_LIST
|
||||
):
|
||||
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."
|
||||
)
|
||||
|
||||
if props["tag"] in LUCIDE_ICON_MAPPING_OVERRIDE:
|
||||
props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[props["tag"]]
|
||||
if tag in LUCIDE_ICON_MAPPING_OVERRIDE:
|
||||
props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[tag]
|
||||
else:
|
||||
props["tag"] = (
|
||||
format.to_title_case(format.to_snake_case(props["tag"])) + "Icon"
|
||||
)
|
||||
props["tag"] = format.to_title_case(format.to_snake_case(tag)) + "Icon"
|
||||
props["alias"] = f"Lucide{props['tag']}"
|
||||
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 = [
|
||||
@ -846,6 +870,7 @@ LUCIDE_ICON_LIST = [
|
||||
"house",
|
||||
"house_plug",
|
||||
"house_plus",
|
||||
"house_wifi",
|
||||
"ice_cream_bowl",
|
||||
"ice_cream_cone",
|
||||
"id_card",
|
||||
@ -1534,6 +1559,7 @@ LUCIDE_ICON_LIST = [
|
||||
"trending_up_down",
|
||||
"triangle",
|
||||
"triangle_alert",
|
||||
"triangle_dashed",
|
||||
"triangle_right",
|
||||
"trophy",
|
||||
"truck",
|
||||
|
@ -104,12 +104,60 @@ class Icon(LucideIconComponent):
|
||||
Raises:
|
||||
AttributeError: The errors tied to bad usage of the Icon component.
|
||||
ValueError: If the icon tag is invalid.
|
||||
TypeError: If the icon name is not a string.
|
||||
|
||||
Returns:
|
||||
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 = [
|
||||
"a_arrow_down",
|
||||
"a_arrow_up",
|
||||
@ -889,6 +937,7 @@ LUCIDE_ICON_LIST = [
|
||||
"house",
|
||||
"house_plug",
|
||||
"house_plus",
|
||||
"house_wifi",
|
||||
"ice_cream_bowl",
|
||||
"ice_cream_cone",
|
||||
"id_card",
|
||||
@ -1577,6 +1626,7 @@ LUCIDE_ICON_LIST = [
|
||||
"trending_up_down",
|
||||
"triangle",
|
||||
"triangle_alert",
|
||||
"triangle_dashed",
|
||||
"triangle_right",
|
||||
"trophy",
|
||||
"truck",
|
||||
|
@ -8,7 +8,7 @@ from functools import lru_cache
|
||||
from hashlib import md5
|
||||
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.utils import types
|
||||
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"),
|
||||
"h6": lambda value: Heading.create(value, as_="h6", size="1", margin_y="0.5em"),
|
||||
"p": lambda value: Text.create(value, margin_y="1em"),
|
||||
"ul": lambda value: UnorderedList.create(value, margin_y="1em"), # type: ignore
|
||||
"ol": lambda value: OrderedList.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"),
|
||||
"li": lambda value: ListItem.create(value, margin_y="0.5em"),
|
||||
"a": lambda value: Link.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()
|
||||
],
|
||||
]
|
||||
@ -327,7 +327,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
||||
if tag != "codeblock"
|
||||
# For codeblock, the mapping for some cases returns an array of elements. Let's join them into a string.
|
||||
else ternary_operation(
|
||||
ARRAY_ISARRAY.call(_CHILDREN), # type: ignore
|
||||
ARRAY_ISARRAY.call(_CHILDREN), # pyright: ignore [reportArgumentType]
|
||||
_CHILDREN.to(list).join("\n"),
|
||||
_CHILDREN,
|
||||
).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.
|
||||
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.
|
||||
|
||||
Args:
|
||||
@ -409,7 +411,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
||||
return custom_code_list
|
||||
|
||||
@staticmethod
|
||||
def _component_map_hash(component_map) -> str:
|
||||
def _component_map_hash(component_map: dict) -> str:
|
||||
inp = str(
|
||||
{tag: component(_MOCK_ARG) for tag, component in component_map.items()}
|
||||
).encode()
|
||||
@ -425,7 +427,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
||||
for _component in self.component_map.values():
|
||||
comp = _component(_MOCK_ARG)
|
||||
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"""
|
||||
function {self._get_component_map_name()} () {{
|
||||
{formatted_hooks}
|
||||
|
@ -28,9 +28,9 @@ class MomentDelta:
|
||||
class Moment(NoSSRComponent):
|
||||
"""The Moment component."""
|
||||
|
||||
tag: str = "Moment"
|
||||
tag: str | None = "Moment"
|
||||
is_default = True
|
||||
library: str = "react-moment"
|
||||
library: str | None = "react-moment"
|
||||
lib_dependencies: List[str] = ["moment"]
|
||||
|
||||
# How often the date update (how often time update / 0 to disable).
|
||||
|
@ -1,13 +1,17 @@
|
||||
"""Image component from next/image."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Literal, Optional, Union
|
||||
|
||||
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 .base import NextComponent
|
||||
|
||||
DEFAULT_W_H = "100%"
|
||||
|
||||
|
||||
class Image(NextComponent):
|
||||
"""Display an image."""
|
||||
@ -53,7 +57,7 @@ class Image(NextComponent):
|
||||
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".
|
||||
blurDataURL: Var[str]
|
||||
blur_data_url: Var[str]
|
||||
|
||||
# Fires when the image has loaded.
|
||||
on_load: EventHandler[no_args_event_spec]
|
||||
@ -80,10 +84,18 @@ class Image(NextComponent):
|
||||
Returns:
|
||||
_type_: _description_
|
||||
"""
|
||||
style = props.get("style", {})
|
||||
DEFAULT_W_H = "100%"
|
||||
if "blurDataURL" in props:
|
||||
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]):
|
||||
props[prop_name] = prop_value
|
||||
|
||||
|
@ -11,6 +11,8 @@ from reflex.vars.base import Var
|
||||
|
||||
from .base import NextComponent
|
||||
|
||||
DEFAULT_W_H = "100%"
|
||||
|
||||
class Image(NextComponent):
|
||||
@overload
|
||||
@classmethod
|
||||
@ -30,7 +32,7 @@ class Image(NextComponent):
|
||||
loading: Optional[
|
||||
Union[Literal["eager", "lazy"], Var[Literal["eager", "lazy"]]]
|
||||
] = None,
|
||||
blurDataURL: Optional[Union[Var[str], str]] = None,
|
||||
blur_data_url: Optional[Union[Var[str], str]] = None,
|
||||
style: Optional[Style] = None,
|
||||
key: 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.
|
||||
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.
|
||||
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_error: Fires when the image has an error.
|
||||
style: The style of the component.
|
||||
|
@ -17,4 +17,4 @@ class NextLink(Component):
|
||||
href: Var[str]
|
||||
|
||||
# Whether to pass the href prop to the child.
|
||||
pass_href: Var[bool] = True # type: ignore
|
||||
pass_href: Var[bool] = Var.create(True)
|
||||
|
@ -18,8 +18,8 @@ try:
|
||||
Template = layout.Template
|
||||
except ImportError:
|
||||
console.warn("Plotly is not installed. Please run `pip install plotly`.")
|
||||
Figure = Any # type: ignore
|
||||
Template = Any # type: ignore
|
||||
Figure = Any
|
||||
Template = Any
|
||||
|
||||
|
||||
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"
|
||||
|
||||
lib_dependencies: List[str] = ["plotly.js@2.35.2"]
|
||||
lib_dependencies: List[str] = ["plotly.js@2.35.3"]
|
||||
|
||||
tag = "Plot"
|
||||
|
||||
is_default = True
|
||||
|
||||
# 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.
|
||||
layout: Var[Dict]
|
||||
|
||||
# The template for visual appearance of the graph.
|
||||
template: Var[Template] # type: ignore
|
||||
template: Var[Template] # pyright: ignore [reportInvalidTypeForm]
|
||||
|
||||
# The config of the graph.
|
||||
config: Var[Dict]
|
||||
|
@ -48,7 +48,7 @@ class PropsBase(Base):
|
||||
class NoExtrasAllowedProps(Base):
|
||||
"""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.
|
||||
|
||||
Args:
|
||||
@ -62,13 +62,13 @@ class NoExtrasAllowedProps(Base):
|
||||
try:
|
||||
super().__init__(**kwargs)
|
||||
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())
|
||||
raise InvalidPropValueError(
|
||||
f"Invalid prop(s) {invalid_fields} for {component_name!r}. Supported props are {supported_props_str}"
|
||||
) from None
|
||||
|
||||
class Config:
|
||||
class Config: # pyright: ignore [reportIncompatibleVariableOverride]
|
||||
"""Pydantic config."""
|
||||
|
||||
arbitrary_types_allowed = True
|
||||
|
@ -55,7 +55,7 @@ from .themes.layout.container import container as container
|
||||
from .themes.layout.flex import flex as flex
|
||||
from .themes.layout.grid import grid as grid
|
||||
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 unordered_list as unordered_list
|
||||
from .themes.layout.section import section as section
|
||||
|
@ -196,8 +196,9 @@ class AccordionItem(AccordionComponent):
|
||||
|
||||
# The header of the accordion item.
|
||||
header: Var[Union[Component, str]]
|
||||
|
||||
# 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] = [
|
||||
"AccordionHeader",
|
||||
@ -485,11 +486,11 @@ to {
|
||||
Returns:
|
||||
The style of the component.
|
||||
"""
|
||||
slideDown = LiteralVar.create(
|
||||
slide_down = LiteralVar.create(
|
||||
"${slideDown} var(--animation-duration) var(--animation-easing)",
|
||||
)
|
||||
|
||||
slideUp = LiteralVar.create(
|
||||
slide_up = LiteralVar.create(
|
||||
"${slideUp} var(--animation-duration) var(--animation-easing)",
|
||||
)
|
||||
|
||||
@ -503,8 +504,8 @@ to {
|
||||
"display": "block",
|
||||
"height": "var(--space-3)",
|
||||
},
|
||||
"&[data-state='open']": {"animation": slideDown},
|
||||
"&[data-state='closed']": {"animation": slideUp},
|
||||
"&[data-state='open']": {"animation": slide_down},
|
||||
"&[data-state='closed']": {"animation": slide_up},
|
||||
_inherited_variant_selector("classic"): {
|
||||
"color": "var(--accent-contrast)",
|
||||
},
|
||||
|
@ -308,7 +308,9 @@ class AccordionItem(AccordionComponent):
|
||||
value: Optional[Union[Var[str], str]] = None,
|
||||
disabled: Optional[Union[Var[bool], bool]] = 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[
|
||||
Union[
|
||||
Literal[
|
||||
|
@ -66,7 +66,7 @@ class DrawerRoot(DrawerComponent):
|
||||
scroll_lock_timeout: Var[int]
|
||||
|
||||
# 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.
|
||||
should_scale_background: Var[bool]
|
||||
@ -83,7 +83,7 @@ class DrawerTrigger(DrawerComponent):
|
||||
alias = "Vaul" + tag
|
||||
|
||||
# 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
|
||||
def create(cls, *children: Any, **props: Any) -> Component:
|
||||
|
@ -81,7 +81,7 @@ class DrawerRoot(DrawerComponent):
|
||||
snap_points: Optional[List[Union[float, str]]] = None,
|
||||
fade_from_index: 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,
|
||||
close_threshold: Optional[Union[Var[float], float]] = 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.
|
||||
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
|
||||
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.
|
||||
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.
|
||||
@ -567,7 +567,7 @@ class Drawer(ComponentNamespace):
|
||||
snap_points: Optional[List[Union[float, str]]] = None,
|
||||
fade_from_index: 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,
|
||||
close_threshold: Optional[Union[Var[float], float]] = 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.
|
||||
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
|
||||
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.
|
||||
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.
|
||||
|
@ -132,10 +132,10 @@ class FormRoot(FormComponent, HTMLForm):
|
||||
on_submit: Optional[
|
||||
Union[
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||
],
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||
],
|
||||
]
|
||||
] = None,
|
||||
@ -608,10 +608,10 @@ class Form(FormRoot):
|
||||
on_submit: Optional[
|
||||
Union[
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||
],
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||
],
|
||||
]
|
||||
] = None,
|
||||
@ -741,10 +741,10 @@ class FormNamespace(ComponentNamespace):
|
||||
on_submit: Optional[
|
||||
Union[
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||
],
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||
],
|
||||
]
|
||||
] = None,
|
||||
|
@ -83,7 +83,7 @@ class ProgressIndicator(ProgressComponent):
|
||||
"&[data_state='loading']": {
|
||||
"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)",
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ def on_value_event_spec(
|
||||
Returns:
|
||||
The event handler spec.
|
||||
"""
|
||||
return (value,) # type: ignore
|
||||
return (value,)
|
||||
|
||||
|
||||
class SliderRoot(SliderComponent):
|
||||
|
@ -17,7 +17,7 @@ rx.text(
|
||||
|
||||
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.core.cond import Cond, color_mode_cond, cond
|
||||
@ -78,17 +78,19 @@ position_map: Dict[str, List[str]] = {
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
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(
|
||||
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:
|
||||
props.setdefault(prop, default)
|
||||
|
||||
@ -115,12 +117,12 @@ class ColorModeIconButton(IconButton):
|
||||
Returns:
|
||||
The button component.
|
||||
"""
|
||||
position = props.pop("position", None)
|
||||
position: str | Var = props.pop("position", None)
|
||||
allow_system = props.pop("allow_system", False)
|
||||
|
||||
# position is used to set nice defaults for positioning the icon button
|
||||
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, "top", "2rem")
|
||||
_set_var_default(props, position, "left", "2rem")
|
||||
@ -142,7 +144,7 @@ class ColorModeIconButton(IconButton):
|
||||
|
||||
if allow_system:
|
||||
|
||||
def color_mode_item(_color_mode):
|
||||
def color_mode_item(_color_mode: str):
|
||||
return dropdown_menu.item(
|
||||
_color_mode.title(), on_click=set_color_mode(_color_mode)
|
||||
)
|
||||
|
@ -22,6 +22,8 @@ from ..base import (
|
||||
|
||||
LiteralButtonSize = Literal["1", "2", "3", "4"]
|
||||
|
||||
RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
|
||||
|
||||
|
||||
class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
|
||||
"""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."
|
||||
)
|
||||
if "size" in props:
|
||||
RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
|
||||
|
||||
if isinstance(props["size"], str):
|
||||
children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]]
|
||||
else:
|
||||
|
@ -14,6 +14,7 @@ from reflex.vars.base import Var
|
||||
from ..base import RadixLoadingProp, RadixThemesComponent
|
||||
|
||||
LiteralButtonSize = Literal["1", "2", "3", "4"]
|
||||
RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
|
||||
|
||||
class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
|
||||
@overload
|
||||
|
@ -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.
|
||||
required: Var[bool]
|
||||
|
||||
_valid_parents: list[str] = ["RadioCardsRoot"]
|
||||
|
||||
|
||||
class RadioCards(SimpleNamespace):
|
||||
"""RadioCards components namespace."""
|
||||
|
@ -155,7 +155,7 @@ class HighLevelRadioGroup(RadixThemesComponent):
|
||||
if isinstance(default_value, str) or (
|
||||
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:
|
||||
default_value = LiteralVar.create(default_value).to_string()
|
||||
|
||||
|
@ -96,5 +96,17 @@ class TextArea(RadixThemesComponent, elements.Textarea):
|
||||
return DebounceInput.create(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
|
||||
|
@ -268,4 +268,6 @@ class TextArea(RadixThemesComponent, elements.Textarea):
|
||||
"""
|
||||
...
|
||||
|
||||
def add_style(self): ...
|
||||
|
||||
text_area = TextArea.create
|
||||
|
@ -105,7 +105,7 @@ class TextFieldRoot(elements.Input, RadixThemesComponent):
|
||||
(value_var := Var.create(value))._var_type
|
||||
):
|
||||
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.create(""),
|
||||
|
@ -28,6 +28,9 @@ LiteralStickyType = Literal[
|
||||
]
|
||||
|
||||
|
||||
ARIA_LABEL_KEY = "aria_label"
|
||||
|
||||
|
||||
# The Tooltip inherits props from the Tooltip.Root, Tooltip.Portal, Tooltip.Content
|
||||
class Tooltip(RadixThemesComponent):
|
||||
"""Floating element that provides a control with contextual information via pointer or focus."""
|
||||
@ -104,7 +107,6 @@ class Tooltip(RadixThemesComponent):
|
||||
Returns:
|
||||
The created component.
|
||||
"""
|
||||
ARIA_LABEL_KEY = "aria_label"
|
||||
if props.get(ARIA_LABEL_KEY) is not None:
|
||||
props[format.to_kebab_case(ARIA_LABEL_KEY)] = props.pop(ARIA_LABEL_KEY)
|
||||
|
||||
|
@ -14,6 +14,7 @@ from ..base import RadixThemesComponent
|
||||
LiteralSideType = Literal["top", "right", "bottom", "left"]
|
||||
LiteralAlignType = Literal["start", "center", "end"]
|
||||
LiteralStickyType = Literal["partial", "always"]
|
||||
ARIA_LABEL_KEY = "aria_label"
|
||||
|
||||
class Tooltip(RadixThemesComponent):
|
||||
@overload
|
||||
|
@ -9,7 +9,7 @@ from .container import container as container
|
||||
from .flex import flex as flex
|
||||
from .grid import grid as grid
|
||||
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 unordered_list as unordered_list
|
||||
from .section import section as section
|
||||
|
@ -72,7 +72,7 @@ class BaseList(Component, MarkdownComponentMap):
|
||||
if isinstance(items, Var):
|
||||
children = [Foreach.create(items, ListItem.create)]
|
||||
else:
|
||||
children = [ListItem.create(item) for item in items] # type: ignore
|
||||
children = [ListItem.create(item) for item in items]
|
||||
props["direction"] = "column"
|
||||
style = props.setdefault("style", {})
|
||||
style["list_style_type"] = list_style_type
|
||||
@ -189,7 +189,7 @@ ordered_list = list_ns.ordered
|
||||
unordered_list = list_ns.unordered
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
def __getattr__(name: Any):
|
||||
# special case for when accessing list to avoid shadowing
|
||||
# python's built in list object.
|
||||
if name == "list":
|
||||
|
@ -49,14 +49,14 @@ class VStack(Stack):
|
||||
"""A vertical stack component."""
|
||||
|
||||
# The direction of the stack.
|
||||
direction: Var[LiteralFlexDirection] = "column" # type: ignore
|
||||
direction: Var[LiteralFlexDirection] = Var.create("column")
|
||||
|
||||
|
||||
class HStack(Stack):
|
||||
"""A horizontal stack component."""
|
||||
|
||||
# The direction of the stack.
|
||||
direction: Var[LiteralFlexDirection] = "row" # type: ignore
|
||||
direction: Var[LiteralFlexDirection] = Var.create("row")
|
||||
|
||||
|
||||
stack = Stack.create
|
||||
|
@ -60,7 +60,7 @@ class Link(RadixThemesComponent, A, MemoizationLeaf, MarkdownComponentMap):
|
||||
Returns:
|
||||
The import dict.
|
||||
"""
|
||||
return next_link._get_imports() # type: ignore
|
||||
return next_link._get_imports() # pyright: ignore [reportReturnType]
|
||||
|
||||
@classmethod
|
||||
def create(cls, *children, **props) -> Component:
|
||||
|
@ -47,7 +47,7 @@ class Text(elements.Span, RadixThemesComponent, MarkdownComponentMap):
|
||||
as_child: Var[bool]
|
||||
|
||||
# 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"
|
||||
size: Var[Responsive[LiteralTextSize]]
|
||||
@ -71,7 +71,7 @@ class Text(elements.Span, RadixThemesComponent, MarkdownComponentMap):
|
||||
class Span(Text):
|
||||
"""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):
|
||||
|
@ -39,7 +39,7 @@ class ReactPlayer(NoSSRComponent):
|
||||
loop: Var[bool]
|
||||
|
||||
# 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
|
||||
light: Var[bool]
|
||||
|
@ -70,6 +70,8 @@ _SUBMOD_ATTRS: dict = {
|
||||
"Label",
|
||||
"label_list",
|
||||
"LabelList",
|
||||
"cell",
|
||||
"Cell",
|
||||
],
|
||||
"polar": [
|
||||
"pie",
|
||||
|
@ -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 scatter_chart as scatter_chart
|
||||
from .charts import treemap as treemap
|
||||
from .general import Cell as Cell
|
||||
from .general import GraphingTooltip as GraphingTooltip
|
||||
from .general import Label as Label
|
||||
from .general import LabelList as LabelList
|
||||
from .general import Legend as Legend
|
||||
from .general import ResponsiveContainer as ResponsiveContainer
|
||||
from .general import cell as cell
|
||||
from .general import graphing_tooltip as graphing_tooltip
|
||||
from .general import label as label
|
||||
from .general import label_list as label_list
|
||||
|
@ -25,10 +25,10 @@ class ChartBase(RechartsCharts):
|
||||
"""A component that wraps a Recharts charts."""
|
||||
|
||||
# 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.
|
||||
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
|
||||
on_click: EventHandler[no_args_event_spec]
|
||||
@ -69,7 +69,7 @@ class ChartBase(RechartsCharts):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create(cls, *children, **props) -> Component:
|
||||
def create(cls, *children: Any, **props: Any) -> Component:
|
||||
"""Create a chart component.
|
||||
|
||||
Args:
|
||||
@ -84,19 +84,19 @@ class ChartBase(RechartsCharts):
|
||||
cls._ensure_valid_dimension("width", width)
|
||||
cls._ensure_valid_dimension("height", height)
|
||||
|
||||
dim_props = {
|
||||
"width": width if width is not None else "100%",
|
||||
"height": height if height is not None else "100%",
|
||||
}
|
||||
# Provide min dimensions so the graph always appears, even if the outer container is zero-size.
|
||||
if width is None:
|
||||
dim_props["min_width"] = 200
|
||||
if height is None:
|
||||
dim_props["min_height"] = 100
|
||||
# Ensure that the min_height and min_width are set to prevent the chart from collapsing.
|
||||
# We are using small values so that height and width can still be used over min_height and min_width.
|
||||
# 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.
|
||||
min_height = props.pop("min_height", 10)
|
||||
min_width = props.pop("min_width", 10)
|
||||
|
||||
return ResponsiveContainer.create(
|
||||
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"
|
||||
|
||||
# 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%"
|
||||
height: Var[Union[str, int]] = "100%" # type: ignore
|
||||
height: Var[Union[str, int]] = Var.create("100%")
|
||||
|
||||
# data of treemap. Array
|
||||
data: Var[List[Dict[str, Any]]]
|
||||
|
@ -36,11 +36,11 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
|
||||
# The height of chart container. Can be a number or string. Default: "100%"
|
||||
height: Var[Union[int, str]]
|
||||
|
||||
# The minimum width of chart container. Number
|
||||
min_width: Var[int]
|
||||
# The minimum width of chart container. Number or string.
|
||||
min_width: Var[Union[int, str]]
|
||||
|
||||
# The minimum height of chart container. Number
|
||||
min_height: Var[int]
|
||||
# The minimum height of chart container. Number or string.
|
||||
min_height: Var[Union[int, str]]
|
||||
|
||||
# If specified a positive number, debounced function will be used to handle the resize event. Default: 0
|
||||
debounce: Var[int]
|
||||
@ -242,8 +242,23 @@ class LabelList(Recharts):
|
||||
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
|
||||
legend = Legend.create
|
||||
graphing_tooltip = GraphingTooltip.create
|
||||
label = Label.create
|
||||
label_list = LabelList.create
|
||||
cell = Cell.create
|
||||
|
@ -22,8 +22,8 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
|
||||
aspect: Optional[Union[Var[int], int]] = None,
|
||||
width: 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_height: Optional[Union[Var[int], int]] = None,
|
||||
min_width: Optional[Union[Var[Union[int, str]], int, str]] = None,
|
||||
min_height: Optional[Union[Var[Union[int, str]], int, str]] = None,
|
||||
debounce: Optional[Union[Var[int], int]] = None,
|
||||
style: Optional[Style] = 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
|
||||
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%"
|
||||
min_width: The minimum width of chart container. Number
|
||||
min_height: The minimum height of chart container. Number
|
||||
min_width: The minimum width of chart container. Number or string.
|
||||
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
|
||||
on_resize: If specified provides a callback providing the updated chart width and height values.
|
||||
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
|
||||
legend = Legend.create
|
||||
graphing_tooltip = GraphingTooltip.create
|
||||
label = Label.create
|
||||
label_list = LabelList.create
|
||||
cell = Cell.create
|
||||
|
@ -64,7 +64,7 @@ class Pie(Recharts):
|
||||
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
|
||||
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
|
||||
label_line: Var[bool]
|
||||
@ -73,7 +73,7 @@ class Pie(Recharts):
|
||||
data: Var[List[Dict[str, Any]]]
|
||||
|
||||
# Valid children components
|
||||
_valid_children: List[str] = ["Cell", "LabelList"]
|
||||
_valid_children: List[str] = ["Cell", "LabelList", "Bare"]
|
||||
|
||||
# Stoke color. Default: rx.color("accent", 9)
|
||||
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""A component that wraps a recharts lib."""
|
||||
|
||||
from typing import Dict, Literal
|
||||
from typing import Literal
|
||||
|
||||
from reflex.components.component import Component, MemoizationLeaf, NoSSRComponent
|
||||
|
||||
@ -8,16 +8,16 @@ from reflex.components.component import Component, MemoizationLeaf, NoSSRCompone
|
||||
class Recharts(Component):
|
||||
"""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}
|
||||
|
||||
|
||||
class RechartsCharts(NoSSRComponent, MemoizationLeaf):
|
||||
"""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"]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user