Merge remote-tracking branch 'origin/main' into masenf/cache-stateful-imports

This commit is contained in:
Masen Furer 2025-02-14 09:14:41 -08:00
commit 5950336745
No known key found for this signature in database
GPG Key ID: B0008AD22B3B3A95
67 changed files with 2733 additions and 1693 deletions

2
.github/codeql-config.yml vendored Normal file
View File

@ -0,0 +1,2 @@
paths-ignore:
- "**/tests/**"

View File

@ -70,56 +70,6 @@ jobs:
env: env:
GITHUB_SHA: ${{ github.sha }} GITHUB_SHA: ${{ github.sha }}
simple-apps-benchmarks: # This app tests the compile times of various compoonents and pages
if: github.event.pull_request.merged == true
env:
OUTPUT_FILE: benchmarks.json
timeout-minutes: 50
strategy:
# Prioritize getting more information out of the workflow (even if something fails)
fail-fast: false
matrix:
# Show OS combos first in GUI
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.10.16", "3.11.11", "3.12.8"]
exclude:
- os: windows-latest
python-version: "3.10.16"
- os: windows-latest
python-version: "3.11.11"
# keep only one python version for MacOS
- os: macos-latest
python-version: "3.10.16"
- os: macos-latest
python-version: "3.11.11"
include:
- os: windows-latest
python-version: "3.10.11"
- os: windows-latest
python-version: "3.11.9"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
python-version: ${{ matrix.python-version }}
run-poetry-install: true
create-venv-at-path: .venv
- name: Run benchmark tests
env:
APP_HARNESS_HEADLESS: 1
PYTHONUNBUFFERED: 1
run: |
poetry run pytest -v benchmarks/ --benchmark-json=${{ env.OUTPUT_FILE }} -s
- name: Upload benchmark results
# Only run if the database creds are available in this context.
run:
poetry run python benchmarks/benchmark_compile_times.py --os "${{ matrix.os }}"
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
--benchmark-json "${{ env.OUTPUT_FILE }}" --branch-name "${{ github.head_ref || github.ref_name }}"
--event-type "${{ github.event_name }}" --pr-id "${{ github.event.pull_request.id }}"
reflex-dist-size: # This job is used to calculate the size of the Reflex distribution (wheel file) reflex-dist-size: # This job is used to calculate the size of the Reflex distribution (wheel file)
if: github.event.pull_request.merged == true if: github.event.pull_request.merged == true
timeout-minutes: 30 timeout-minutes: 30

103
.github/workflows/codeql.yml vendored Normal file
View File

@ -0,0 +1,103 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
schedule:
- cron: "36 7 * * 4"
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: javascript-typescript
build-mode: none
- language: python
build-mode: none
- language: actions
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
config-file: .github/codeql-config.yml
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

@ -94,26 +94,6 @@ jobs:
# Check that npm is home # Check that npm is home
npm -v npm -v
poetry run bash scripts/integration.sh ./reflex-examples/counter dev poetry run bash scripts/integration.sh ./reflex-examples/counter dev
- name: Measure and upload .web size
run:
poetry run python benchmarks/benchmark_web_size.py --os "${{ matrix.os }}"
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
--pr-id "${{ github.event.pull_request.id }}"
--branch-name "${{ github.head_ref || github.ref_name }}"
--path ./reflex-examples/counter/.web
--app-name "counter"
- name: Install hyperfine
run: cargo install hyperfine
- name: Benchmark imports
working-directory: ./reflex-examples/counter
run: hyperfine --warmup 3 "export POETRY_VIRTUALENVS_PATH=../../.venv; poetry run python counter/counter.py" --show-output --export-json "${{ env.OUTPUT_FILE }}" --shell bash
- name: Upload Benchmarks
run:
poetry run python benchmarks/benchmark_imports.py --os "${{ matrix.os }}"
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
--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 - name: Install requirements for nba proxy example
working-directory: ./reflex-examples/nba-proxy working-directory: ./reflex-examples/nba-proxy
run: | run: |
@ -174,12 +154,6 @@ jobs:
# Check that npm is home # Check that npm is home
npm -v npm -v
poetry run bash scripts/integration.sh ./reflex-web prod poetry run bash scripts/integration.sh ./reflex-web prod
- name: Measure and upload .web size
run:
poetry run python benchmarks/benchmark_web_size.py --os "${{ matrix.os }}"
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
--pr-id "${{ github.event.pull_request.id }}" --branch-name "${{ github.head_ref || github.ref_name }}"
--app-name "reflex-web" --path ./reflex-web/.web
rx-shout-from-template: rx-shout-from-template:
strategy: strategy:
@ -243,9 +217,3 @@ jobs:
# Check that npm is home # Check that npm is home
npm -v npm -v
poetry run bash scripts/integration.sh ./reflex-web prod poetry run bash scripts/integration.sh ./reflex-web prod
- name: Measure and upload .web size
run:
poetry run python benchmarks/benchmark_web_size.py --os "${{ matrix.os }}"
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
--pr-id "${{ github.event.pull_request.id }}" --branch-name "${{ github.head_ref || github.ref_name }}"
--app-name "reflex-web" --path ./reflex-web/.web

View File

@ -3,7 +3,7 @@ fail_fast: true
repos: repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit - repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.9.3 rev: v0.9.6
hooks: hooks:
- id: ruff-format - id: ruff-format
args: [reflex, tests] args: [reflex, tests]

View File

@ -1,376 +0,0 @@
"""Benchmark tests for apps with varying component numbers."""
from __future__ import annotations
import functools
import time
from typing import Generator
import pytest
from benchmarks import WINDOWS_SKIP_REASON
from reflex import constants
from reflex.compiler import utils
from reflex.testing import AppHarness, chdir
from reflex.utils import build
from reflex.utils.prerequisites import get_web_dir
web_pages = get_web_dir() / constants.Dirs.PAGES
def render_component(num: int):
"""Generate a number of components based on num.
Args:
num: number of components to produce.
Returns:
The rendered number of components.
"""
import reflex as rx
return [
rx.fragment(
rx.box(
rx.accordion.root(
rx.accordion.item(
header="Full Ingredients",
content="Yes. It's built with accessibility in mind.",
font_size="3em",
),
rx.accordion.item(
header="Applications",
content="Yes. It's unstyled by default, giving you freedom over the look and feel.",
),
collapsible=True,
variant="ghost",
width="25rem",
),
padding_top="20px",
),
rx.box(
rx.drawer.root(
rx.drawer.trigger(
rx.button("Open Drawer with snap points"), as_child=True
),
rx.drawer.overlay(),
rx.drawer.portal(
rx.drawer.content(
rx.flex(
rx.drawer.title("Drawer Content"),
rx.drawer.description("Drawer description"),
rx.drawer.close(
rx.button("Close Button"),
as_child=True,
),
direction="column",
margin="5em",
align_items="center",
),
top="auto",
height="100%",
flex_direction="column",
background_color="var(--green-3)",
),
),
snap_points=["148px", "355px", 1],
),
),
rx.box(
rx.callout(
"You will need admin privileges to install and access this application.",
icon="info",
size="3",
),
),
rx.box(
rx.table.root(
rx.table.header(
rx.table.row(
rx.table.column_header_cell("Full name"),
rx.table.column_header_cell("Email"),
rx.table.column_header_cell("Group"),
),
),
rx.table.body(
rx.table.row(
rx.table.row_header_cell("Danilo Sousa"),
rx.table.cell("danilo@example.com"),
rx.table.cell("Developer"),
),
rx.table.row(
rx.table.row_header_cell("Zahra Ambessa"),
rx.table.cell("zahra@example.com"),
rx.table.cell("Admin"),
),
rx.table.row(
rx.table.row_header_cell("Jasper Eriksson"),
rx.table.cell("jasper@example.com"),
rx.table.cell("Developer"),
),
),
)
),
)
] * num
def AppWithTenComponentsOnePage():
"""A reflex app with roughly 10 components on one page."""
import reflex as rx
def index() -> rx.Component:
return rx.center(rx.vstack(*render_component(1)))
app = rx.App(_state=rx.State)
app.add_page(index)
def AppWithHundredComponentOnePage():
"""A reflex app with roughly 100 components on one page."""
import reflex as rx
def index() -> rx.Component:
return rx.center(rx.vstack(*render_component(100)))
app = rx.App(_state=rx.State)
app.add_page(index)
def AppWithThousandComponentsOnePage():
"""A reflex app with roughly 1000 components on one page."""
import reflex as rx
def index() -> rx.Component:
return rx.center(rx.vstack(*render_component(1000)))
app = rx.App(_state=rx.State)
app.add_page(index)
@pytest.fixture(scope="session")
def app_with_10_components(
tmp_path_factory,
) -> Generator[AppHarness, None, None]:
"""Start Blank Template app at tmp_path via AppHarness.
Args:
tmp_path_factory: pytest tmp_path_factory fixture
Yields:
running AppHarness instance
"""
root = tmp_path_factory.mktemp("app10components")
yield AppHarness.create(
root=root,
app_source=functools.partial(
AppWithTenComponentsOnePage,
render_component=render_component, # pyright: ignore [reportCallIssue]
),
)
@pytest.fixture(scope="session")
def app_with_100_components(
tmp_path_factory,
) -> Generator[AppHarness, None, None]:
"""Start Blank Template app at tmp_path via AppHarness.
Args:
tmp_path_factory: pytest tmp_path_factory fixture
Yields:
running AppHarness instance
"""
root = tmp_path_factory.mktemp("app100components")
yield AppHarness.create(
root=root,
app_source=functools.partial(
AppWithHundredComponentOnePage,
render_component=render_component, # pyright: ignore [reportCallIssue]
),
)
@pytest.fixture(scope="session")
def app_with_1000_components(
tmp_path_factory,
) -> Generator[AppHarness, None, None]:
"""Create an app with 1000 components at tmp_path via AppHarness.
Args:
tmp_path_factory: pytest tmp_path_factory fixture
Yields:
an AppHarness instance
"""
root = tmp_path_factory.mktemp("app1000components")
yield AppHarness.create(
root=root,
app_source=functools.partial(
AppWithThousandComponentsOnePage,
render_component=render_component, # pyright: ignore [reportCallIssue]
),
)
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
@pytest.mark.benchmark(
group="Compile time of varying component numbers",
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_10_compile_time_cold(benchmark, app_with_10_components):
"""Test the compile time on a cold start for an app with roughly 10 components.
Args:
benchmark: The benchmark fixture.
app_with_10_components: The app harness.
"""
def setup():
with chdir(app_with_10_components.app_path):
utils.empty_dir(web_pages, ["_app.js"])
app_with_10_components._initialize_app()
build.setup_frontend(app_with_10_components.app_path)
def benchmark_fn():
with chdir(app_with_10_components.app_path):
app_with_10_components.app_instance._compile()
benchmark.pedantic(benchmark_fn, setup=setup, rounds=10)
@pytest.mark.benchmark(
group="Compile time of varying component numbers",
min_rounds=5,
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_10_compile_time_warm(benchmark, app_with_10_components):
"""Test the compile time on a warm start for an app with roughly 10 components.
Args:
benchmark: The benchmark fixture.
app_with_10_components: The app harness.
"""
with chdir(app_with_10_components.app_path):
app_with_10_components._initialize_app()
build.setup_frontend(app_with_10_components.app_path)
def benchmark_fn():
with chdir(app_with_10_components.app_path):
app_with_10_components.app_instance._compile()
benchmark(benchmark_fn)
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
@pytest.mark.benchmark(
group="Compile time of varying component numbers",
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_100_compile_time_cold(benchmark, app_with_100_components):
"""Test the compile time on a cold start for an app with roughly 100 components.
Args:
benchmark: The benchmark fixture.
app_with_100_components: The app harness.
"""
def setup():
with chdir(app_with_100_components.app_path):
utils.empty_dir(web_pages, ["_app.js"])
app_with_100_components._initialize_app()
build.setup_frontend(app_with_100_components.app_path)
def benchmark_fn():
with chdir(app_with_100_components.app_path):
app_with_100_components.app_instance._compile()
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
@pytest.mark.benchmark(
group="Compile time of varying component numbers",
min_rounds=5,
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_100_compile_time_warm(benchmark, app_with_100_components):
"""Test the compile time on a warm start for an app with roughly 100 components.
Args:
benchmark: The benchmark fixture.
app_with_100_components: The app harness.
"""
with chdir(app_with_100_components.app_path):
app_with_100_components._initialize_app()
build.setup_frontend(app_with_100_components.app_path)
def benchmark_fn():
with chdir(app_with_100_components.app_path):
app_with_100_components.app_instance._compile()
benchmark(benchmark_fn)
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
@pytest.mark.benchmark(
group="Compile time of varying component numbers",
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_1000_compile_time_cold(benchmark, app_with_1000_components):
"""Test the compile time on a cold start for an app with roughly 1000 components.
Args:
benchmark: The benchmark fixture.
app_with_1000_components: The app harness.
"""
def setup():
with chdir(app_with_1000_components.app_path):
utils.empty_dir(web_pages, keep_files=["_app.js"])
app_with_1000_components._initialize_app()
build.setup_frontend(app_with_1000_components.app_path)
def benchmark_fn():
with chdir(app_with_1000_components.app_path):
app_with_1000_components.app_instance._compile()
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
@pytest.mark.benchmark(
group="Compile time of varying component numbers",
min_rounds=5,
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_1000_compile_time_warm(benchmark, app_with_1000_components):
"""Test the compile time on a warm start for an app with roughly 1000 components.
Args:
benchmark: The benchmark fixture.
app_with_1000_components: The app harness.
"""
with chdir(app_with_1000_components.app_path):
app_with_1000_components._initialize_app()
build.setup_frontend(app_with_1000_components.app_path)
def benchmark_fn():
with chdir(app_with_1000_components.app_path):
app_with_1000_components.app_instance._compile()
benchmark(benchmark_fn)

View File

@ -1,579 +0,0 @@
"""Benchmark tests for apps with varying page numbers."""
from __future__ import annotations
import functools
import time
from typing import Generator
import pytest
from benchmarks import WINDOWS_SKIP_REASON
from reflex import constants
from reflex.compiler import utils
from reflex.testing import AppHarness, chdir
from reflex.utils import build
from reflex.utils.prerequisites import get_web_dir
web_pages = get_web_dir() / constants.Dirs.PAGES
def render_multiple_pages(app, num: int):
"""Add multiple pages based on num.
Args:
app: The App object.
num: number of pages to render.
"""
from typing import Tuple
from rxconfig import config # pyright: ignore [reportMissingImports]
import reflex as rx
docs_url = "https://reflex.dev/docs/getting-started/introduction/"
filename = f"{config.app_name}/{config.app_name}.py"
college = [
"Stanford University",
"Arizona",
"Arizona state",
"Baylor",
"Boston College",
"Boston University",
]
class State(rx.State):
"""The app state."""
position: str
college: str
age: Tuple[int, int] = (18, 50)
salary: Tuple[int, int] = (0, 25000000)
comp1 = rx.center(
rx.theme_panel(),
rx.vstack(
rx.heading("Welcome to Reflex!", size="9"),
rx.text("Get started by editing ", rx.code(filename)),
rx.button(
"Check out our docs!",
on_click=lambda: rx.redirect(docs_url),
size="4",
),
align="center",
spacing="7",
font_size="2em",
),
height="100vh",
)
comp2 = rx.vstack(
rx.hstack(
rx.vstack(
rx.select(
["C", "PF", "SF", "PG", "SG"],
placeholder="Select a position. (All)",
on_change=State.set_position, # pyright: ignore [reportAttributeAccessIssue]
size="3",
),
rx.select(
college,
placeholder="Select a college. (All)",
on_change=State.set_college, # pyright: ignore [reportAttributeAccessIssue]
size="3",
),
),
rx.vstack(
rx.vstack(
rx.hstack(
rx.badge("Min Age: ", State.age[0]),
rx.divider(orientation="vertical"),
rx.badge("Max Age: ", State.age[1]),
),
rx.slider(
default_value=[18, 50],
min=18,
max=50,
on_value_commit=State.set_age, # pyright: ignore [reportAttributeAccessIssue]
),
align_items="left",
width="100%",
),
rx.vstack(
rx.hstack(
rx.badge("Min Sal: ", State.salary[0] // 1000000, "M"),
rx.divider(orientation="vertical"),
rx.badge("Max Sal: ", State.salary[1] // 1000000, "M"),
),
rx.slider(
default_value=[0, 25000000],
min=0,
max=25000000,
on_value_commit=State.set_salary, # pyright: ignore [reportAttributeAccessIssue]
),
align_items="left",
width="100%",
),
),
spacing="4",
),
width="100%",
)
for i in range(1, num + 1):
if i % 2 == 1:
app.add_page(comp1, route=f"page{i}")
else:
app.add_page(comp2, route=f"page{i}")
def AppWithOnePage():
"""A reflex app with one page."""
from rxconfig import config # pyright: ignore [reportMissingImports]
import reflex as rx
docs_url = "https://reflex.dev/docs/getting-started/introduction/"
filename = f"{config.app_name}/{config.app_name}.py"
class State(rx.State):
"""The app state."""
pass
def index() -> rx.Component:
return rx.center(
rx.input(
id="token", value=State.router.session.client_token, is_read_only=True
),
rx.vstack(
rx.heading("Welcome to Reflex!", size="9"),
rx.text("Get started by editing ", rx.code(filename)),
rx.button(
"Check out our docs!",
on_click=lambda: rx.redirect(docs_url),
size="4",
),
align="center",
spacing="7",
font_size="2em",
),
height="100vh",
)
app = rx.App(_state=rx.State)
app.add_page(index)
def AppWithTenPages():
"""A reflex app with 10 pages."""
import reflex as rx
app = rx.App(_state=rx.State)
render_multiple_pages(app, 10)
def AppWithHundredPages():
"""A reflex app with 100 pages."""
import reflex as rx
app = rx.App(_state=rx.State)
render_multiple_pages(app, 100)
def AppWithThousandPages():
"""A reflex app with Thousand pages."""
import reflex as rx
app = rx.App(_state=rx.State)
render_multiple_pages(app, 1000)
def AppWithTenThousandPages():
"""A reflex app with ten thousand pages."""
import reflex as rx
app = rx.App(_state=rx.State)
render_multiple_pages(app, 10000)
@pytest.fixture(scope="session")
def app_with_one_page(
tmp_path_factory,
) -> Generator[AppHarness, None, None]:
"""Create an app with 10000 pages at tmp_path via AppHarness.
Args:
tmp_path_factory: pytest tmp_path_factory fixture
Yields:
an AppHarness instance
"""
root = tmp_path_factory.mktemp("app1")
yield AppHarness.create(root=root, app_source=AppWithOnePage)
@pytest.fixture(scope="session")
def app_with_ten_pages(
tmp_path_factory,
) -> Generator[AppHarness, None, None]:
"""Create an app with 10 pages at tmp_path via AppHarness.
Args:
tmp_path_factory: pytest tmp_path_factory fixture
Yields:
an AppHarness instance
"""
root = tmp_path_factory.mktemp("app10")
yield AppHarness.create(
root=root,
app_source=functools.partial(
AppWithTenPages,
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
),
)
@pytest.fixture(scope="session")
def app_with_hundred_pages(
tmp_path_factory,
) -> Generator[AppHarness, None, None]:
"""Create an app with 100 pages at tmp_path via AppHarness.
Args:
tmp_path_factory: pytest tmp_path_factory fixture
Yields:
an AppHarness instance
"""
root = tmp_path_factory.mktemp("app100")
yield AppHarness.create(
root=root,
app_source=functools.partial(
AppWithHundredPages,
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
),
)
@pytest.fixture(scope="session")
def app_with_thousand_pages(
tmp_path_factory,
) -> Generator[AppHarness, None, None]:
"""Create an app with 1000 pages at tmp_path via AppHarness.
Args:
tmp_path_factory: pytest tmp_path_factory fixture
Yields:
an AppHarness instance
"""
root = tmp_path_factory.mktemp("app1000")
yield AppHarness.create(
root=root,
app_source=functools.partial(
AppWithThousandPages,
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
),
)
@pytest.fixture(scope="session")
def app_with_ten_thousand_pages(
tmp_path_factory,
) -> Generator[AppHarness, None, None]:
"""Create an app with 10000 pages at tmp_path via AppHarness.
Args:
tmp_path_factory: pytest tmp_path_factory fixture
Yields:
running AppHarness instance
"""
root = tmp_path_factory.mktemp("app10000")
yield AppHarness.create(
root=root,
app_source=functools.partial(
AppWithTenThousandPages,
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
),
)
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
@pytest.mark.benchmark(
group="Compile time of varying page numbers",
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_1_compile_time_cold(benchmark, app_with_one_page):
"""Test the compile time on a cold start for an app with 1 page.
Args:
benchmark: The benchmark fixture.
app_with_one_page: The app harness.
"""
def setup():
with chdir(app_with_one_page.app_path):
utils.empty_dir(web_pages, keep_files=["_app.js"])
app_with_one_page._initialize_app()
build.setup_frontend(app_with_one_page.app_path)
def benchmark_fn():
with chdir(app_with_one_page.app_path):
app_with_one_page.app_instance._compile()
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
app_with_one_page._reload_state_module()
@pytest.mark.benchmark(
group="Compile time of varying page numbers",
min_rounds=5,
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_1_compile_time_warm(benchmark, app_with_one_page):
"""Test the compile time on a warm start for an app with 1 page.
Args:
benchmark: The benchmark fixture.
app_with_one_page: The app harness.
"""
with chdir(app_with_one_page.app_path):
app_with_one_page._initialize_app()
build.setup_frontend(app_with_one_page.app_path)
def benchmark_fn():
with chdir(app_with_one_page.app_path):
app_with_one_page.app_instance._compile()
benchmark(benchmark_fn)
app_with_one_page._reload_state_module()
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
@pytest.mark.benchmark(
group="Compile time of varying page numbers",
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_10_compile_time_cold(benchmark, app_with_ten_pages):
"""Test the compile time on a cold start for an app with 10 page.
Args:
benchmark: The benchmark fixture.
app_with_ten_pages: The app harness.
"""
def setup():
with chdir(app_with_ten_pages.app_path):
utils.empty_dir(web_pages, keep_files=["_app.js"])
app_with_ten_pages._initialize_app()
build.setup_frontend(app_with_ten_pages.app_path)
def benchmark_fn():
with chdir(app_with_ten_pages.app_path):
app_with_ten_pages.app_instance._compile()
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
app_with_ten_pages._reload_state_module()
@pytest.mark.benchmark(
group="Compile time of varying page numbers",
min_rounds=5,
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_10_compile_time_warm(benchmark, app_with_ten_pages):
"""Test the compile time on a warm start for an app with 10 page.
Args:
benchmark: The benchmark fixture.
app_with_ten_pages: The app harness.
"""
with chdir(app_with_ten_pages.app_path):
app_with_ten_pages._initialize_app()
build.setup_frontend(app_with_ten_pages.app_path)
def benchmark_fn():
with chdir(app_with_ten_pages.app_path):
app_with_ten_pages.app_instance._compile()
benchmark(benchmark_fn)
app_with_ten_pages._reload_state_module()
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
@pytest.mark.benchmark(
group="Compile time of varying page numbers",
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_100_compile_time_cold(benchmark, app_with_hundred_pages):
"""Test the compile time on a cold start for an app with 100 page.
Args:
benchmark: The benchmark fixture.
app_with_hundred_pages: The app harness.
"""
def setup():
with chdir(app_with_hundred_pages.app_path):
utils.empty_dir(web_pages, keep_files=["_app.js"])
app_with_hundred_pages._initialize_app()
build.setup_frontend(app_with_hundred_pages.app_path)
def benchmark_fn():
with chdir(app_with_hundred_pages.app_path):
app_with_hundred_pages.app_instance._compile()
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
app_with_hundred_pages._reload_state_module()
@pytest.mark.benchmark(
group="Compile time of varying page numbers",
min_rounds=5,
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_100_compile_time_warm(benchmark, app_with_hundred_pages):
"""Test the compile time on a warm start for an app with 100 page.
Args:
benchmark: The benchmark fixture.
app_with_hundred_pages: The app harness.
"""
with chdir(app_with_hundred_pages.app_path):
app_with_hundred_pages._initialize_app()
build.setup_frontend(app_with_hundred_pages.app_path)
def benchmark_fn():
with chdir(app_with_hundred_pages.app_path):
app_with_hundred_pages.app_instance._compile()
benchmark(benchmark_fn)
app_with_hundred_pages._reload_state_module()
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
@pytest.mark.benchmark(
group="Compile time of varying page numbers",
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_1000_compile_time_cold(benchmark, app_with_thousand_pages):
"""Test the compile time on a cold start for an app with 1000 page.
Args:
benchmark: The benchmark fixture.
app_with_thousand_pages: The app harness.
"""
def setup():
with chdir(app_with_thousand_pages.app_path):
utils.empty_dir(web_pages, keep_files=["_app.js"])
app_with_thousand_pages._initialize_app()
build.setup_frontend(app_with_thousand_pages.app_path)
def benchmark_fn():
with chdir(app_with_thousand_pages.app_path):
app_with_thousand_pages.app_instance._compile()
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
app_with_thousand_pages._reload_state_module()
@pytest.mark.benchmark(
group="Compile time of varying page numbers",
min_rounds=5,
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_1000_compile_time_warm(benchmark, app_with_thousand_pages):
"""Test the compile time on a warm start for an app with 1000 page.
Args:
benchmark: The benchmark fixture.
app_with_thousand_pages: The app harness.
"""
with chdir(app_with_thousand_pages.app_path):
app_with_thousand_pages._initialize_app()
build.setup_frontend(app_with_thousand_pages.app_path)
def benchmark_fn():
with chdir(app_with_thousand_pages.app_path):
app_with_thousand_pages.app_instance._compile()
benchmark(benchmark_fn)
app_with_thousand_pages._reload_state_module()
@pytest.mark.skip
@pytest.mark.benchmark(
group="Compile time of varying page numbers",
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_10000_compile_time_cold(benchmark, app_with_ten_thousand_pages):
"""Test the compile time on a cold start for an app with 10000 page.
Args:
benchmark: The benchmark fixture.
app_with_ten_thousand_pages: The app harness.
"""
def setup():
with chdir(app_with_ten_thousand_pages.app_path):
utils.empty_dir(web_pages, keep_files=["_app.js"])
app_with_ten_thousand_pages._initialize_app()
build.setup_frontend(app_with_ten_thousand_pages.app_path)
def benchmark_fn():
with chdir(app_with_ten_thousand_pages.app_path):
app_with_ten_thousand_pages.app_instance._compile()
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
app_with_ten_thousand_pages._reload_state_module()
@pytest.mark.skip
@pytest.mark.benchmark(
group="Compile time of varying page numbers",
min_rounds=5,
timer=time.perf_counter,
disable_gc=True,
warmup=False,
)
def test_app_10000_compile_time_warm(benchmark, app_with_ten_thousand_pages):
"""Test the compile time on a warm start for an app with 10000 page.
Args:
benchmark: The benchmark fixture.
app_with_ten_thousand_pages: The app harness.
"""
def benchmark_fn():
with chdir(app_with_ten_thousand_pages.app_path):
app_with_ten_thousand_pages.app_instance._compile()
benchmark(benchmark_fn)
app_with_ten_thousand_pages._reload_state_module()

395
poetry.lock generated
View File

@ -403,75 +403,76 @@ markers = {main = "(platform_system == \"Windows\" or os_name == \"nt\") and (py
[[package]] [[package]]
name = "coverage" name = "coverage"
version = "7.6.10" version = "7.6.12"
description = "Code coverage measurement for Python" description = "Code coverage measurement for Python"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["dev"] groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [ files = [
{file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"},
{file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"},
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"},
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"},
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"},
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"},
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"},
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"},
{file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"},
{file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"},
{file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"},
{file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"},
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"},
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"},
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"},
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"},
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"},
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"},
{file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"},
{file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"},
{file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"},
{file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"},
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"},
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"},
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"},
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"},
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"},
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"},
{file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"},
{file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"},
{file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"},
{file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"},
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"},
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"},
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"},
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"},
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"},
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"},
{file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"},
{file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"},
{file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"},
{file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"},
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"},
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"},
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"},
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"},
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"},
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"},
{file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"},
{file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"},
{file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, {file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"},
{file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, {file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"},
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, {file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"},
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"},
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"},
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"},
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"},
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"},
{file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, {file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"},
{file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, {file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"},
{file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"},
{file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"},
{file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"},
] ]
[package.dependencies] [package.dependencies]
@ -482,40 +483,44 @@ toml = ["tomli"]
[[package]] [[package]]
name = "cryptography" name = "cryptography"
version = "44.0.0" version = "44.0.1"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false optional = false
python-versions = "!=3.9.0,!=3.9.1,>=3.7" python-versions = "!=3.9.0,!=3.9.1,>=3.7"
groups = ["main"] groups = ["main"]
markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and (python_version <= \"3.11\" or python_version >= \"3.12\")" markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and (python_version <= \"3.11\" or python_version >= \"3.12\")"
files = [ files = [
{file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, {file = "cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026"},
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd"},
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0"},
{file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, {file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf"},
{file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, {file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864"},
{file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, {file = "cryptography-44.0.1-cp37-abi3-win32.whl", hash = "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, {file = "cryptography-44.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, {file = "cryptography-44.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7"},
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a"},
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c"},
{file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62"},
{file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41"},
{file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, {file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b"},
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, {file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7"},
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, {file = "cryptography-44.0.1-cp39-abi3-win32.whl", hash = "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9"},
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, {file = "cryptography-44.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f"},
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, {file = "cryptography-44.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183"},
{file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12"},
{file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fed5cd6102bb4eb843e3315d2bf25fede494509bddadb81e03a859c1bc17b83"},
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f4daefc971c2d1f82f03097dc6f216744a6cd2ac0f04c68fb935ea2ba2a0d420"},
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94f99f2b943b354a5b6307d7e8d19f5c423a794462bde2bf310c770ba052b1c4"},
{file = "cryptography-44.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d9c5b9f698a83c8bd71e0f4d3f9f839ef244798e5ffe96febfa9714717db7af7"},
{file = "cryptography-44.0.1.tar.gz", hash = "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14"},
] ]
[package.dependencies] [package.dependencies]
@ -528,7 +533,7 @@ nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"]
pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
sdist = ["build (>=1.0.0)"] sdist = ["build (>=1.0.0)"]
ssh = ["bcrypt (>=3.1.5)"] ssh = ["bcrypt (>=3.1.5)"]
test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.1)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
test-randomorder = ["pytest-randomly"] test-randomorder = ["pytest-randomly"]
[[package]] [[package]]
@ -852,15 +857,15 @@ test = ["coverage[toml]", "pretend", "pytest", "pytest-cov"]
[[package]] [[package]]
name = "identify" name = "identify"
version = "2.6.6" version = "2.6.7"
description = "File identification library for Python" description = "File identification library for Python"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["dev"] groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [ files = [
{file = "identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881"}, {file = "identify-2.6.7-py2.py3-none-any.whl", hash = "sha256:155931cb617a401807b09ecec6635d6c692d180090a1cedca8ef7d58ba5b6aa0"},
{file = "identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251"}, {file = "identify-2.6.7.tar.gz", hash = "sha256:3fa266b42eba321ee0b2bb0936a6a6b9e36a1351cbb69055b3082f4193035684"},
] ]
[package.extras] [package.extras]
@ -1074,15 +1079,15 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
[[package]] [[package]]
name = "mako" name = "mako"
version = "1.3.8" version = "1.3.9"
description = "A super-fast templating language that borrows the best ideas from the existing templating languages." description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"] groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [ files = [
{file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627"}, {file = "Mako-1.3.9-py3-none-any.whl", hash = "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1"},
{file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8"}, {file = "mako-1.3.9.tar.gz", hash = "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac"},
] ]
[package.dependencies] [package.dependencies]
@ -1558,25 +1563,25 @@ type = ["mypy (>=1.11.2)"]
[[package]] [[package]]
name = "playwright" name = "playwright"
version = "1.49.1" version = "1.50.0"
description = "A high-level API to automate web browsers" description = "A high-level API to automate web browsers"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["dev"] groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [ files = [
{file = "playwright-1.49.1-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:1041ffb45a0d0bc44d698d3a5aa3ac4b67c9bd03540da43a0b70616ad52592b8"}, {file = "playwright-1.50.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:f36d754a6c5bd9bf7f14e8f57a2aea6fd08f39ca4c8476481b9c83e299531148"},
{file = "playwright-1.49.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9f38ed3d0c1f4e0a6d1c92e73dd9a61f8855133249d6f0cec28648d38a7137be"}, {file = "playwright-1.50.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:40f274384591dfd27f2b014596250b2250c843ed1f7f4ef5d2960ecb91b4961e"},
{file = "playwright-1.49.1-py3-none-macosx_11_0_universal2.whl", hash = "sha256:3be48c6d26dc819ca0a26567c1ae36a980a0303dcd4249feb6f59e115aaddfb8"}, {file = "playwright-1.50.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:9922ef9bcd316995f01e220acffd2d37a463b4ad10fd73e388add03841dfa230"},
{file = "playwright-1.49.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:753ca90ee31b4b03d165cfd36e477309ebf2b4381953f2a982ff612d85b147d2"}, {file = "playwright-1.50.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:8fc628c492d12b13d1f347137b2ac6c04f98197ff0985ef0403a9a9ee0d39131"},
{file = "playwright-1.49.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd9bc8dab37aa25198a01f555f0a2e2c3813fe200fef018ac34dfe86b34994b9"}, {file = "playwright-1.50.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcff35f72db2689a79007aee78f1b0621a22e6e3d6c1f58aaa9ac805bf4497c"},
{file = "playwright-1.49.1-py3-none-win32.whl", hash = "sha256:43b304be67f096058e587dac453ece550eff87b8fbed28de30f4f022cc1745bb"}, {file = "playwright-1.50.0-py3-none-win32.whl", hash = "sha256:3b906f4d351260016a8c5cc1e003bb341651ae682f62213b50168ed581c7558a"},
{file = "playwright-1.49.1-py3-none-win_amd64.whl", hash = "sha256:47b23cb346283278f5b4d1e1990bcb6d6302f80c0aa0ca93dd0601a1400191df"}, {file = "playwright-1.50.0-py3-none-win_amd64.whl", hash = "sha256:1859423da82de631704d5e3d88602d755462b0906824c1debe140979397d2e8d"},
] ]
[package.dependencies] [package.dependencies]
greenlet = "3.1.1" greenlet = ">=3.1.1,<4.0.0"
pyee = "12.0.0" pyee = ">=12,<13"
[[package]] [[package]]
name = "plotly" name = "plotly"
@ -1828,15 +1833,15 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]] [[package]]
name = "pyee" name = "pyee"
version = "12.0.0" version = "12.1.1"
description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own" description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"] groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [ files = [
{file = "pyee-12.0.0-py3-none-any.whl", hash = "sha256:7b14b74320600049ccc7d0e0b1becd3b4bd0a03c745758225e31a59f4095c990"}, {file = "pyee-12.1.1-py3-none-any.whl", hash = "sha256:18a19c650556bb6b32b406d7f017c8f513aceed1ef7ca618fb65de7bd2d347ef"},
{file = "pyee-12.0.0.tar.gz", hash = "sha256:c480603f4aa2927d4766eb41fa82793fe60a82cbfdb8d688e0d08c55a534e145"}, {file = "pyee-12.1.1.tar.gz", hash = "sha256:bbc33c09e2ff827f74191e3e5bbc6be7da02f627b7ec30d86f5ce1a6fb2424a3"},
] ]
[package.dependencies] [package.dependencies]
@ -2311,15 +2316,15 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"
[[package]] [[package]]
name = "reflex-hosting-cli" name = "reflex-hosting-cli"
version = "0.1.34" version = "0.1.35"
description = "Reflex Hosting CLI" description = "Reflex Hosting CLI"
optional = false optional = false
python-versions = "<4.0,>=3.9" python-versions = "<4.0,>=3.9"
groups = ["main"] groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [ files = [
{file = "reflex_hosting_cli-0.1.34-py3-none-any.whl", hash = "sha256:eabc4dc7bf68e022a9388614c1a35b5ab36b01021df063d0c3356eda0e245264"}, {file = "reflex_hosting_cli-0.1.35-py3-none-any.whl", hash = "sha256:619687be27e6691cb54f6cf038e98d4d622fcf25a85bc9986f8daf52b48e6744"},
{file = "reflex_hosting_cli-0.1.34.tar.gz", hash = "sha256:07be37fda6dcede0a5d4bc1fd1786d9a3df5ad4e49dc1b6ba335418563cfecec"}, {file = "reflex_hosting_cli-0.1.35.tar.gz", hash = "sha256:9a5d02978b900045464a1a5581f3adc6260daaa09e8acf95fd05024cda926ae7"},
] ]
[package.dependencies] [package.dependencies]
@ -2410,31 +2415,31 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.9.3" version = "0.9.6"
description = "An extremely fast Python linter and code formatter, written in Rust." description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["dev"] groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [ files = [
{file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"}, {file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"},
{file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"}, {file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"},
{file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"}, {file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"}, {file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"}, {file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"}, {file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"}, {file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"},
{file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"}, {file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"},
{file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"}, {file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"},
{file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"}, {file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"},
{file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"}, {file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"},
] ]
[[package]] [[package]]
@ -2571,70 +2576,70 @@ files = [
[[package]] [[package]]
name = "sqlalchemy" name = "sqlalchemy"
version = "2.0.37" version = "2.0.38"
description = "Database Abstraction Library" description = "Database Abstraction Library"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"] groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [ files = [
{file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da36c3b0e891808a7542c5c89f224520b9a16c7f5e4d6a1156955605e54aef0e"}, {file = "SQLAlchemy-2.0.38-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5e1d9e429028ce04f187a9f522818386c8b076723cdbe9345708384f49ebcec6"},
{file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e7402ff96e2b073a98ef6d6142796426d705addd27b9d26c3b32dbaa06d7d069"}, {file = "SQLAlchemy-2.0.38-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b87a90f14c68c925817423b0424381f0e16d80fc9a1a1046ef202ab25b19a444"},
{file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6f5d254a22394847245f411a2956976401e84da4288aa70cbcd5190744062c1"}, {file = "SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:402c2316d95ed90d3d3c25ad0390afa52f4d2c56b348f212aa9c8d072a40eee5"},
{file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41296bbcaa55ef5fdd32389a35c710133b097f7b2609d8218c0eabded43a1d84"}, {file = "SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6493bc0eacdbb2c0f0d260d8988e943fee06089cd239bd7f3d0c45d1657a70e2"},
{file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bedee60385c1c0411378cbd4dc486362f5ee88deceea50002772912d798bb00f"}, {file = "SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0561832b04c6071bac3aad45b0d3bb6d2c4f46a8409f0a7a9c9fa6673b41bc03"},
{file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6c67415258f9f3c69867ec02fea1bf6508153709ecbd731a982442a590f2b7e4"}, {file = "SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:49aa2cdd1e88adb1617c672a09bf4ebf2f05c9448c6dbeba096a3aeeb9d4d443"},
{file = "SQLAlchemy-2.0.37-cp310-cp310-win32.whl", hash = "sha256:650dcb70739957a492ad8acff65d099a9586b9b8920e3507ca61ec3ce650bb72"}, {file = "SQLAlchemy-2.0.38-cp310-cp310-win32.whl", hash = "sha256:64aa8934200e222f72fcfd82ee71c0130a9c07d5725af6fe6e919017d095b297"},
{file = "SQLAlchemy-2.0.37-cp310-cp310-win_amd64.whl", hash = "sha256:93d1543cd8359040c02b6614421c8e10cd7a788c40047dbc507ed46c29ae5636"}, {file = "SQLAlchemy-2.0.38-cp310-cp310-win_amd64.whl", hash = "sha256:c57b8e0841f3fce7b703530ed70c7c36269c6d180ea2e02e36b34cb7288c50c7"},
{file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:78361be6dc9073ed17ab380985d1e45e48a642313ab68ab6afa2457354ff692c"}, {file = "SQLAlchemy-2.0.38-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf89e0e4a30714b357f5d46b6f20e0099d38b30d45fa68ea48589faf5f12f62d"},
{file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b661b49d0cb0ab311a189b31e25576b7ac3e20783beb1e1817d72d9d02508bf5"}, {file = "SQLAlchemy-2.0.38-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8455aa60da49cb112df62b4721bd8ad3654a3a02b9452c783e651637a1f21fa2"},
{file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d57bafbab289e147d064ffbd5cca2d7b1394b63417c0636cea1f2e93d16eb9e8"}, {file = "SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53c0d6a859b2db58332e0e6a921582a02c1677cc93d4cbb36fdf49709b327b2"},
{file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa2c0913f02341d25fb858e4fb2031e6b0813494cca1ba07d417674128ce11b"}, {file = "SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c4817dff8cef5697f5afe5fec6bc1783994d55a68391be24cb7d80d2dbc3a6"},
{file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9df21b8d9e5c136ea6cde1c50d2b1c29a2b5ff2b1d610165c23ff250e0704087"}, {file = "SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9cea5b756173bb86e2235f2f871b406a9b9d722417ae31e5391ccaef5348f2c"},
{file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db18ff6b8c0f1917f8b20f8eca35c28bbccb9f83afa94743e03d40203ed83de9"}, {file = "SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40e9cdbd18c1f84631312b64993f7d755d85a3930252f6276a77432a2b25a2f3"},
{file = "SQLAlchemy-2.0.37-cp311-cp311-win32.whl", hash = "sha256:46954173612617a99a64aee103bcd3f078901b9a8dcfc6ae80cbf34ba23df989"}, {file = "SQLAlchemy-2.0.38-cp311-cp311-win32.whl", hash = "sha256:cb39ed598aaf102251483f3e4675c5dd6b289c8142210ef76ba24aae0a8f8aba"},
{file = "SQLAlchemy-2.0.37-cp311-cp311-win_amd64.whl", hash = "sha256:7b7e772dc4bc507fdec4ee20182f15bd60d2a84f1e087a8accf5b5b7a0dcf2ba"}, {file = "SQLAlchemy-2.0.38-cp311-cp311-win_amd64.whl", hash = "sha256:f9d57f1b3061b3e21476b0ad5f0397b112b94ace21d1f439f2db472e568178ae"},
{file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2952748ecd67ed3b56773c185e85fc084f6bdcdec10e5032a7c25a6bc7d682ef"}, {file = "SQLAlchemy-2.0.38-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12d5b06a1f3aeccf295a5843c86835033797fea292c60e72b07bcb5d820e6dd3"},
{file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3151822aa1db0eb5afd65ccfafebe0ef5cda3a7701a279c8d0bf17781a793bb4"}, {file = "SQLAlchemy-2.0.38-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e036549ad14f2b414c725349cce0772ea34a7ab008e9cd67f9084e4f371d1f32"},
{file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaa8039b6d20137a4e02603aba37d12cd2dde7887500b8855356682fc33933f4"}, {file = "SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee3bee874cb1fadee2ff2b79fc9fc808aa638670f28b2145074538d4a6a5028e"},
{file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdba1f73b64530c47b27118b7053b8447e6d6f3c8104e3ac59f3d40c33aa9fd"}, {file = "SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e185ea07a99ce8b8edfc788c586c538c4b1351007e614ceb708fd01b095ef33e"},
{file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1b2690456528a87234a75d1a1644cdb330a6926f455403c8e4f6cad6921f9098"}, {file = "SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b79ee64d01d05a5476d5cceb3c27b5535e6bb84ee0f872ba60d9a8cd4d0e6579"},
{file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf5ae8a9dcf657fd72144a7fd01f243236ea39e7344e579a121c4205aedf07bb"}, {file = "SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afd776cf1ebfc7f9aa42a09cf19feadb40a26366802d86c1fba080d8e5e74bdd"},
{file = "SQLAlchemy-2.0.37-cp312-cp312-win32.whl", hash = "sha256:ea308cec940905ba008291d93619d92edaf83232ec85fbd514dcb329f3192761"}, {file = "SQLAlchemy-2.0.38-cp312-cp312-win32.whl", hash = "sha256:a5645cd45f56895cfe3ca3459aed9ff2d3f9aaa29ff7edf557fa7a23515a3725"},
{file = "SQLAlchemy-2.0.37-cp312-cp312-win_amd64.whl", hash = "sha256:635d8a21577341dfe4f7fa59ec394b346da12420b86624a69e466d446de16aff"}, {file = "SQLAlchemy-2.0.38-cp312-cp312-win_amd64.whl", hash = "sha256:1052723e6cd95312f6a6eff9a279fd41bbae67633415373fdac3c430eca3425d"},
{file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c4096727193762e72ce9437e2a86a110cf081241919ce3fab8e89c02f6b6658"}, {file = "SQLAlchemy-2.0.38-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ecef029b69843b82048c5b347d8e6049356aa24ed644006c9a9d7098c3bd3bfd"},
{file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e4fb5ac86d8fe8151966814f6720996430462e633d225497566b3996966b9bdb"}, {file = "SQLAlchemy-2.0.38-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c8bcad7fc12f0cc5896d8e10fdf703c45bd487294a986903fe032c72201596b"},
{file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e56a139bfe136a22c438478a86f8204c1eb5eed36f4e15c4224e4b9db01cb3e4"}, {file = "SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0ef3f98175d77180ffdc623d38e9f1736e8d86b6ba70bff182a7e68bed7727"},
{file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f95fc8e3f34b5f6b3effb49d10ac97c569ec8e32f985612d9b25dd12d0d2e94"}, {file = "SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ac78898c50e2574e9f938d2e5caa8fe187d7a5b69b65faa1ea4648925b096"},
{file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c505edd429abdfe3643fa3b2e83efb3445a34a9dc49d5f692dd087be966020e0"}, {file = "SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9eb4fa13c8c7a2404b6a8e3772c17a55b1ba18bc711e25e4d6c0c9f5f541b02a"},
{file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:12b0f1ec623cccf058cf21cb544f0e74656618165b083d78145cafde156ea7b6"}, {file = "SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5dba1cdb8f319084f5b00d41207b2079822aa8d6a4667c0f369fce85e34b0c86"},
{file = "SQLAlchemy-2.0.37-cp313-cp313-win32.whl", hash = "sha256:293f9ade06b2e68dd03cfb14d49202fac47b7bb94bffcff174568c951fbc7af2"}, {file = "SQLAlchemy-2.0.38-cp313-cp313-win32.whl", hash = "sha256:eae27ad7580529a427cfdd52c87abb2dfb15ce2b7a3e0fc29fbb63e2ed6f8120"},
{file = "SQLAlchemy-2.0.37-cp313-cp313-win_amd64.whl", hash = "sha256:d70f53a0646cc418ca4853da57cf3ddddbccb8c98406791f24426f2dd77fd0e2"}, {file = "SQLAlchemy-2.0.38-cp313-cp313-win_amd64.whl", hash = "sha256:b335a7c958bc945e10c522c069cd6e5804f4ff20f9a744dd38e748eb602cbbda"},
{file = "SQLAlchemy-2.0.37-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44f569d0b1eb82301b92b72085583277316e7367e038d97c3a1a899d9a05e342"}, {file = "SQLAlchemy-2.0.38-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:40310db77a55512a18827488e592965d3dec6a3f1e3d8af3f8243134029daca3"},
{file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2eae3423e538c10d93ae3e87788c6a84658c3ed6db62e6a61bb9495b0ad16bb"}, {file = "SQLAlchemy-2.0.38-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d3043375dd5bbcb2282894cbb12e6c559654c67b5fffb462fda815a55bf93f7"},
{file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfff7be361048244c3aa0f60b5e63221c5e0f0e509f4e47b8910e22b57d10ae7"}, {file = "SQLAlchemy-2.0.38-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70065dfabf023b155a9c2a18f573e47e6ca709b9e8619b2e04c54d5bcf193178"},
{file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:5bc3339db84c5fb9130ac0e2f20347ee77b5dd2596ba327ce0d399752f4fce39"}, {file = "SQLAlchemy-2.0.38-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:c058b84c3b24812c859300f3b5abf300daa34df20d4d4f42e9652a4d1c48c8a4"},
{file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:84b9f23b0fa98a6a4b99d73989350a94e4a4ec476b9a7dfe9b79ba5939f5e80b"}, {file = "SQLAlchemy-2.0.38-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0398361acebb42975deb747a824b5188817d32b5c8f8aba767d51ad0cc7bb08d"},
{file = "SQLAlchemy-2.0.37-cp37-cp37m-win32.whl", hash = "sha256:51bc9cfef83e0ac84f86bf2b10eaccb27c5a3e66a1212bef676f5bee6ef33ebb"}, {file = "SQLAlchemy-2.0.38-cp37-cp37m-win32.whl", hash = "sha256:a2bc4e49e8329f3283d99840c136ff2cd1a29e49b5624a46a290f04dff48e079"},
{file = "SQLAlchemy-2.0.37-cp37-cp37m-win_amd64.whl", hash = "sha256:8e47f1af09444f87c67b4f1bb6231e12ba6d4d9f03050d7fc88df6d075231a49"}, {file = "SQLAlchemy-2.0.38-cp37-cp37m-win_amd64.whl", hash = "sha256:9cd136184dd5f58892f24001cdce986f5d7e96059d004118d5410671579834a4"},
{file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6b788f14c5bb91db7f468dcf76f8b64423660a05e57fe277d3f4fad7b9dcb7ce"}, {file = "SQLAlchemy-2.0.38-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:665255e7aae5f38237b3a6eae49d2358d83a59f39ac21036413fab5d1e810578"},
{file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521ef85c04c33009166777c77e76c8a676e2d8528dc83a57836b63ca9c69dcd1"}, {file = "SQLAlchemy-2.0.38-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:92f99f2623ff16bd4aaf786ccde759c1f676d39c7bf2855eb0b540e1ac4530c8"},
{file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75311559f5c9881a9808eadbeb20ed8d8ba3f7225bef3afed2000c2a9f4d49b9"}, {file = "SQLAlchemy-2.0.38-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa498d1392216fae47eaf10c593e06c34476ced9549657fca713d0d1ba5f7248"},
{file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cce918ada64c956b62ca2c2af59b125767097ec1dca89650a6221e887521bfd7"}, {file = "SQLAlchemy-2.0.38-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9afbc3909d0274d6ac8ec891e30210563b2c8bdd52ebbda14146354e7a69373"},
{file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9d087663b7e1feabea8c578d6887d59bb00388158e8bff3a76be11aa3f748ca2"}, {file = "SQLAlchemy-2.0.38-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:57dd41ba32430cbcc812041d4de8d2ca4651aeefad2626921ae2a23deb8cd6ff"},
{file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cf95a60b36997dad99692314c4713f141b61c5b0b4cc5c3426faad570b31ca01"}, {file = "SQLAlchemy-2.0.38-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3e35d5565b35b66905b79ca4ae85840a8d40d31e0b3e2990f2e7692071b179ca"},
{file = "SQLAlchemy-2.0.37-cp38-cp38-win32.whl", hash = "sha256:d75ead7dd4d255068ea0f21492ee67937bd7c90964c8f3c2bea83c7b7f81b95f"}, {file = "SQLAlchemy-2.0.38-cp38-cp38-win32.whl", hash = "sha256:f0d3de936b192980209d7b5149e3c98977c3810d401482d05fb6d668d53c1c63"},
{file = "SQLAlchemy-2.0.37-cp38-cp38-win_amd64.whl", hash = "sha256:74bbd1d0a9bacf34266a7907d43260c8d65d31d691bb2356f41b17c2dca5b1d0"}, {file = "SQLAlchemy-2.0.38-cp38-cp38-win_amd64.whl", hash = "sha256:3868acb639c136d98107c9096303d2d8e5da2880f7706f9f8c06a7f961961149"},
{file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:648ec5acf95ad59255452ef759054f2176849662af4521db6cb245263ae4aa33"}, {file = "SQLAlchemy-2.0.38-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07258341402a718f166618470cde0c34e4cec85a39767dce4e24f61ba5e667ea"},
{file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:35bd2df269de082065d4b23ae08502a47255832cc3f17619a5cea92ce478b02b"}, {file = "SQLAlchemy-2.0.38-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a826f21848632add58bef4f755a33d45105d25656a0c849f2dc2df1c71f6f50"},
{file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f581d365af9373a738c49e0c51e8b18e08d8a6b1b15cc556773bcd8a192fa8b"}, {file = "SQLAlchemy-2.0.38-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:386b7d136919bb66ced64d2228b92d66140de5fefb3c7df6bd79069a269a7b06"},
{file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82df02816c14f8dc9f4d74aea4cb84a92f4b0620235daa76dde002409a3fbb5a"}, {file = "SQLAlchemy-2.0.38-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f2951dc4b4f990a4b394d6b382accb33141d4d3bd3ef4e2b27287135d6bdd68"},
{file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94b564e38b344d3e67d2e224f0aec6ba09a77e4582ced41e7bfd0f757d926ec9"}, {file = "SQLAlchemy-2.0.38-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8bf312ed8ac096d674c6aa9131b249093c1b37c35db6a967daa4c84746bc1bc9"},
{file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:955a2a765aa1bd81aafa69ffda179d4fe3e2a3ad462a736ae5b6f387f78bfeb8"}, {file = "SQLAlchemy-2.0.38-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6db316d6e340f862ec059dc12e395d71f39746a20503b124edc255973977b728"},
{file = "SQLAlchemy-2.0.37-cp39-cp39-win32.whl", hash = "sha256:03f0528c53ca0b67094c4764523c1451ea15959bbf0a8a8a3096900014db0278"}, {file = "SQLAlchemy-2.0.38-cp39-cp39-win32.whl", hash = "sha256:c09a6ea87658695e527104cf857c70f79f14e9484605e205217aae0ec27b45fc"},
{file = "SQLAlchemy-2.0.37-cp39-cp39-win_amd64.whl", hash = "sha256:4b12885dc85a2ab2b7d00995bac6d967bffa8594123b02ed21e8eb2205a7584b"}, {file = "SQLAlchemy-2.0.38-cp39-cp39-win_amd64.whl", hash = "sha256:12f5c9ed53334c3ce719155424dc5407aaa4f6cadeb09c5b627e06abb93933a1"},
{file = "SQLAlchemy-2.0.37-py3-none-any.whl", hash = "sha256:a8998bf9f8658bd3839cbc44ddbe982955641863da0c1efe5b00c1ab4f5c16b1"}, {file = "SQLAlchemy-2.0.38-py3-none-any.whl", hash = "sha256:63178c675d4c80def39f1febd625a6333f44c0ba269edd8a468b156394b27753"},
{file = "sqlalchemy-2.0.37.tar.gz", hash = "sha256:12b28d99a9c14eaf4055810df1001557176716de0167b91026e648e65229bffb"}, {file = "sqlalchemy-2.0.38.tar.gz", hash = "sha256:e5a4d82bdb4bf1ac1285a68eab02d253ab73355d9f0fe725a97e1e0fa689decb"},
] ]
[package.dependencies] [package.dependencies]
@ -2999,15 +3004,15 @@ standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)",
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.29.1" version = "20.29.2"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"] groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [ files = [
{file = "virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779"}, {file = "virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a"},
{file = "virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35"}, {file = "virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728"},
] ]
[package.dependencies] [package.dependencies]
@ -3183,4 +3188,4 @@ type = ["pytest-mypy"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.10, <4.0" python-versions = ">=3.10, <4.0"
content-hash = "3b7e6e6e872c68f951f191d85a7d76fe1dd86caf32e2143a53a3152a3686fc7f" content-hash = "7ae644e1c5b910f4fd0d8ab0b530818077a96e5d329b2be1269e967c6b0b3d25"

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "reflex" name = "reflex"
version = "0.7.0dev1" version = "0.7.1dev1"
description = "Web apps in pure Python." description = "Web apps in pure Python."
license = "Apache-2.0" license = "Apache-2.0"
authors = [ authors = [
@ -61,7 +61,7 @@ dill = ">=0.3.8"
toml = ">=0.10.2,<1.0" toml = ">=0.10.2,<1.0"
pytest-asyncio = ">=0.24.0" pytest-asyncio = ">=0.24.0"
pytest-cov = ">=4.0.0,<7.0" pytest-cov = ">=4.0.0,<7.0"
ruff = "0.9.3" ruff = "0.9.6"
pandas = ">=2.1.1,<3.0" pandas = ">=2.1.1,<3.0"
pillow = ">=10.0.0,<12.0" pillow = ">=10.0.0,<12.0"
plotly = ">=5.13.0,<6.0" plotly = ">=5.13.0,<6.0"

View File

@ -11,17 +11,17 @@ import functools
import inspect import inspect
import io import io
import json import json
import multiprocessing
import platform
import sys import sys
import traceback import traceback
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from timeit import default_timer as timer
from types import SimpleNamespace from types import SimpleNamespace
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
AsyncIterator, AsyncIterator,
BinaryIO,
Callable, Callable,
Coroutine, Coroutine,
Dict, Dict,
@ -35,12 +35,15 @@ from typing import (
get_type_hints, get_type_hints,
) )
from fastapi import FastAPI, HTTPException, Request, UploadFile from fastapi import FastAPI, HTTPException, Request
from fastapi import UploadFile as FastAPIUploadFile
from fastapi.middleware import cors from fastapi.middleware import cors
from fastapi.responses import JSONResponse, StreamingResponse from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
from socketio import ASGIApp, AsyncNamespace, AsyncServer from socketio import ASGIApp, AsyncNamespace, AsyncServer
from starlette.datastructures import Headers
from starlette.datastructures import UploadFile as StarletteUploadFile
from starlette_admin.contrib.sqla.admin import Admin from starlette_admin.contrib.sqla.admin import Admin
from starlette_admin.contrib.sqla.view import ModelView from starlette_admin.contrib.sqla.view import ModelView
@ -72,7 +75,7 @@ from reflex.components.core.client_side_routing import (
from reflex.components.core.sticky import sticky from reflex.components.core.sticky import sticky
from reflex.components.core.upload import Upload, get_upload_dir from reflex.components.core.upload import Upload, get_upload_dir
from reflex.components.radix import themes from reflex.components.radix import themes
from reflex.config import environment, get_config from reflex.config import ExecutorType, environment, get_config
from reflex.event import ( from reflex.event import (
_EVENT_FIELDS, _EVENT_FIELDS,
Event, Event,
@ -100,7 +103,15 @@ from reflex.state import (
all_base_state_classes, all_base_state_classes,
code_uses_state_contexts, code_uses_state_contexts,
) )
from reflex.utils import codespaces, console, exceptions, format, prerequisites, types from reflex.utils import (
codespaces,
console,
exceptions,
format,
path_ops,
prerequisites,
types,
)
from reflex.utils.exec import is_prod_mode, is_testing_env from reflex.utils.exec import is_prod_mode, is_testing_env
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
@ -158,11 +169,11 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
return window_alert("\n".join(error_message)) return window_alert("\n".join(error_message))
def default_overlay_component() -> Component: def extra_overlay_function() -> Optional[Component]:
"""Default overlay_component attribute for App. """Extra overlay function to add to the overlay component.
Returns: Returns:
The default overlay_component, which is a connection_modal. The extra overlay function.
""" """
config = get_config() config = get_config()
@ -172,7 +183,8 @@ def default_overlay_component() -> Component:
module, _, function_name = extra_config.rpartition(".") module, _, function_name = extra_config.rpartition(".")
try: try:
module = __import__(module) module = __import__(module)
config_overlay = getattr(module, function_name)() config_overlay = Fragment.create(getattr(module, function_name)())
config_overlay._get_all_imports()
except Exception as e: except Exception as e:
from reflex.compiler.utils import save_error from reflex.compiler.utils import save_error
@ -182,13 +194,27 @@ def default_overlay_component() -> Component:
f"Error loading extra_overlay_function {extra_config}. Error saved to {log_path}" f"Error loading extra_overlay_function {extra_config}. Error saved to {log_path}"
) )
return Fragment.create( return config_overlay
connection_pulser(),
connection_toaster(),
*([config_overlay] if config_overlay else []), def default_overlay_component() -> Component:
*([backend_disabled()] if config.is_reflex_cloud else []), """Default overlay_component attribute for App.
*codespaces.codespaces_auto_redirect(),
) Returns:
The default overlay_component, which is a connection_modal.
"""
config = get_config()
from reflex.components.component import memo
def default_overlay_components():
return Fragment.create(
connection_pulser(),
connection_toaster(),
*([backend_disabled()] if config.is_reflex_cloud else []),
*codespaces.codespaces_auto_redirect(),
)
return Fragment.create(memo(default_overlay_components)())
def default_error_boundary(*children: Component) -> Component: def default_error_boundary(*children: Component) -> Component:
@ -210,6 +236,53 @@ class OverlayFragment(Fragment):
pass pass
@dataclasses.dataclass(frozen=True)
class UploadFile(StarletteUploadFile):
"""A file uploaded to the server.
Args:
file: The standard Python file object (non-async).
filename: The original file name.
size: The size of the file in bytes.
headers: The headers of the request.
"""
file: BinaryIO
path: Optional[Path] = dataclasses.field(default=None)
_deprecated_filename: Optional[str] = dataclasses.field(default=None)
size: Optional[int] = dataclasses.field(default=None)
headers: Headers = dataclasses.field(default_factory=Headers)
@property
def name(self) -> Optional[str]:
"""Get the name of the uploaded file.
Returns:
The name of the uploaded file.
"""
if self.path:
return self.path.name
@property
def filename(self) -> Optional[str]:
"""Get the filename of the uploaded file.
Returns:
The filename of the uploaded file.
"""
console.deprecate(
feature_name="UploadFile.filename",
reason="Use UploadFile.name instead.",
deprecation_version="0.7.1",
removal_version="0.8.0",
)
return self._deprecated_filename
@dataclasses.dataclass( @dataclasses.dataclass(
frozen=True, frozen=True,
) )
@ -260,11 +333,26 @@ class App(MiddlewareMixin, LifespanMixin):
# A component that is present on every page (defaults to the Connection Error banner). # A component that is present on every page (defaults to the Connection Error banner).
overlay_component: Optional[Union[Component, ComponentCallable]] = ( overlay_component: Optional[Union[Component, ComponentCallable]] = (
dataclasses.field(default_factory=default_overlay_component) dataclasses.field(default=None)
) )
# Error boundary component to wrap the app with. # Error boundary component to wrap the app with.
error_boundary: Optional[ComponentCallable] = default_error_boundary error_boundary: Optional[ComponentCallable] = dataclasses.field(default=None)
# App wraps to be applied to the whole app. Expected to be a dictionary of (order, name) to a function that takes whether the state is enabled and optionally returns a component.
app_wraps: Dict[tuple[int, str], Callable[[bool], Optional[Component]]] = (
dataclasses.field(
default_factory=lambda: {
(55, "ErrorBoundary"): (
lambda stateful: default_error_boundary() if stateful else None
),
(5, "Overlay"): (
lambda stateful: default_overlay_component() if stateful else None
),
(4, "ExtraOverlay"): lambda stateful: extra_overlay_function(),
}
)
)
# Components to add to the head of every page. # Components to add to the head of every page.
head_components: List[Component] = dataclasses.field(default_factory=list) head_components: List[Component] = dataclasses.field(default_factory=list)
@ -322,6 +410,9 @@ class App(MiddlewareMixin, LifespanMixin):
[Exception], Union[EventSpec, List[EventSpec], None] [Exception], Union[EventSpec, List[EventSpec], None]
] = default_backend_exception_handler ] = default_backend_exception_handler
# Put the toast provider in the app wrap.
bundle_toaster: bool = True
@property @property
def api(self) -> FastAPI | None: def api(self) -> FastAPI | None:
"""Get the backend api. """Get the backend api.
@ -560,7 +651,9 @@ class App(MiddlewareMixin, LifespanMixin):
Returns: Returns:
The generated component. The generated component.
""" """
return component if isinstance(component, Component) else component() from reflex.compiler.compiler import into_component
return into_component(component)
def add_page( def add_page(
self, self,
@ -888,25 +981,6 @@ class App(MiddlewareMixin, LifespanMixin):
for k, component in self._pages.items(): for k, component in self._pages.items():
self._pages[k] = self._add_overlay_to_component(component) self._pages[k] = self._add_overlay_to_component(component)
def _add_error_boundary_to_component(self, component: Component) -> Component:
if self.error_boundary is None:
return component
component = self.error_boundary(*component.children)
return component
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:
self.error_boundary = None
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)
def _setup_sticky_badge(self): def _setup_sticky_badge(self):
"""Add the sticky badge to the app.""" """Add the sticky badge to the app."""
for k, component in self._pages.items(): for k, component in self._pages.items():
@ -1021,9 +1095,14 @@ class App(MiddlewareMixin, LifespanMixin):
should_compile = self._should_compile() should_compile = self._should_compile()
if not should_compile: if not should_compile:
for route in self._unevaluated_pages: if self.bundle_toaster:
console.debug(f"Evaluating page: {route}") from reflex.components.sonner.toast import Toaster
self._compile_page(route, save_page=should_compile)
Toaster.is_used = True
with console.timing("Evaluate Pages (Backend)"):
for route in self._unevaluated_pages:
console.debug(f"Evaluating page: {route}")
self._compile_page(route, save_page=should_compile)
# Add the optional endpoints (_upload) # Add the optional endpoints (_upload)
self._add_optional_endpoints() self._add_optional_endpoints()
@ -1049,24 +1128,51 @@ class App(MiddlewareMixin, LifespanMixin):
+ adhoc_steps_without_executor, + adhoc_steps_without_executor,
) )
for route in self._unevaluated_pages: if self.bundle_toaster:
console.debug(f"Evaluating page: {route}") from reflex.components.component import memo
self._compile_page(route, save_page=should_compile) from reflex.components.sonner.toast import toast
progress.advance(task)
internal_toast_provider = toast.provider()
@memo
def memoized_toast_provider():
return internal_toast_provider
toast_provider = Fragment.create(memoized_toast_provider())
app_wrappers[(1, "ToasterProvider")] = toast_provider
with console.timing("Evaluate Pages (Frontend)"):
performance_metrics: list[tuple[str, float]] = []
for route in self._unevaluated_pages:
console.debug(f"Evaluating page: {route}")
start = timer()
self._compile_page(route, save_page=should_compile)
end = timer()
performance_metrics.append((route, end - start))
progress.advance(task)
console.debug(
"Slowest pages:\n"
+ "\n".join(
f"{route}: {time * 1000:.1f}ms"
for route, time in sorted(
performance_metrics, key=lambda x: x[1], reverse=True
)[:10]
)
)
# Add the optional endpoints (_upload) # Add the optional endpoints (_upload)
self._add_optional_endpoints() self._add_optional_endpoints()
self._validate_var_dependencies() self._validate_var_dependencies()
self._setup_overlay_component() self._setup_overlay_component()
self._setup_error_boundary() if is_prod_mode() and config.show_built_with_reflex:
if config.show_built_with_reflex:
self._setup_sticky_badge() self._setup_sticky_badge()
progress.advance(task) progress.advance(task)
# Store the compile results. # Store the compile results.
compile_results = [] compile_results: list[tuple[str, str]] = []
progress.advance(task) progress.advance(task)
@ -1086,14 +1192,32 @@ class App(MiddlewareMixin, LifespanMixin):
# Add the custom components from the page to the set. # Add the custom components from the page to the set.
custom_components |= component._get_all_custom_components() custom_components |= component._get_all_custom_components()
# Perform auto-memoization of stateful components. # Add the app wraps to the app.
( for key, app_wrap in self.app_wraps.items():
stateful_components_path, component = app_wrap(self._state is not None)
stateful_components_code, if component is not None:
page_components, app_wrappers[key] = component
) = compiler.compile_stateful_components(self._pages.values())
progress.advance(task) for component in app_wrappers.values():
custom_components |= component._get_all_custom_components()
if self.error_boundary:
console.deprecate(
feature_name="App.error_boundary",
reason="Use app_wraps instead.",
deprecation_version="0.7.1",
removal_version="0.8.0",
)
app_wrappers[(55, "ErrorBoundary")] = self.error_boundary()
# Perform auto-memoization of stateful components.
with console.timing("Auto-memoize StatefulComponents"):
(
stateful_components_path,
stateful_components_code,
page_components,
) = 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. # 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:
@ -1116,33 +1240,30 @@ class App(MiddlewareMixin, LifespanMixin):
progress.advance(task) progress.advance(task)
# Use a forking process pool, if possible. Much faster, especially for large sites. # Copy the assets.
# Fallback to ThreadPoolExecutor as something that will always work. assets_src = Path.cwd() / constants.Dirs.APP_ASSETS
executor = None if assets_src.is_dir():
if ( with console.timing("Copy assets"):
platform.system() in ("Linux", "Darwin") path_ops.update_directory_tree(
and (number_of_processes := environment.REFLEX_COMPILE_PROCESSES.get()) src=assets_src,
is not None dest=(
): Path.cwd() / prerequisites.get_web_dir() / constants.Dirs.PUBLIC
executor = concurrent.futures.ProcessPoolExecutor( ),
max_workers=number_of_processes or None, )
mp_context=multiprocessing.get_context("fork"),
) executor = ExecutorType.get_executor_from_environment()
else:
executor = concurrent.futures.ThreadPoolExecutor(
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
)
for route, component in zip(self._pages, page_components, strict=True): for route, component in zip(self._pages, page_components, strict=True):
ExecutorSafeFunctions.COMPONENTS[route] = component ExecutorSafeFunctions.COMPONENTS[route] = component
ExecutorSafeFunctions.STATE = self._state ExecutorSafeFunctions.STATE = self._state
with executor: with console.timing("Compile to Javascript"), executor as executor:
result_futures = [] result_futures: list[concurrent.futures.Future[tuple[str, str]]] = []
def _submit_work(fn: Callable, *args, **kwargs): def _submit_work(fn: Callable[..., tuple[str, str]], *args, **kwargs):
f = executor.submit(fn, *args, **kwargs) f = executor.submit(fn, *args, **kwargs)
f.add_done_callback(lambda _: progress.advance(task))
result_futures.append(f) result_futures.append(f)
# Compile the pre-compiled pages. # Compile the pre-compiled pages.
@ -1168,9 +1289,10 @@ class App(MiddlewareMixin, LifespanMixin):
_submit_work(compiler.remove_tailwind_from_postcss) _submit_work(compiler.remove_tailwind_from_postcss)
# Wait for all compilation tasks to complete. # Wait for all compilation tasks to complete.
for future in concurrent.futures.as_completed(result_futures): compile_results.extend(
compile_results.append(future.result()) future.result()
progress.advance(task) for future in concurrent.futures.as_completed(result_futures)
)
app_root = self._app_root(app_wrappers=app_wrappers) app_root = self._app_root(app_wrappers=app_wrappers)
@ -1195,17 +1317,20 @@ class App(MiddlewareMixin, LifespanMixin):
progress.advance(task) progress.advance(task)
# Compile custom components. # Compile custom components.
*custom_components_result, custom_components_imports = ( (
compiler.compile_components(custom_components) custom_components_output,
) custom_components_result,
compile_results.append(custom_components_result) custom_components_imports,
) = compiler.compile_components(custom_components)
compile_results.append((custom_components_output, custom_components_result))
all_imports.update(custom_components_imports) all_imports.update(custom_components_imports)
progress.advance(task) progress.advance(task)
progress.stop() progress.stop()
# Install frontend packages. # Install frontend packages.
self._get_frontend_packages(all_imports) with console.timing("Install Frontend Packages"):
self._get_frontend_packages(all_imports)
# Setup the next.config.js # Setup the next.config.js
transpile_packages = [ transpile_packages = [
@ -1231,8 +1356,9 @@ class App(MiddlewareMixin, LifespanMixin):
# Remove pages that are no longer in the app. # Remove pages that are no longer in the app.
p.unlink() p.unlink()
for output_path, code in compile_results: with console.timing("Write to Disk"):
compiler_utils.write_page(output_path, code) for output_path, code in compile_results:
compiler_utils.write_page(output_path, code)
# Pickle dynamic states # Pickle dynamic states
if self._state is not None: if self._state is not None:
@ -1549,7 +1675,7 @@ def upload(app: App):
The upload function. The upload function.
""" """
async def upload_file(request: Request, files: List[UploadFile]): async def upload_file(request: Request, files: List[FastAPIUploadFile]):
"""Upload a file. """Upload a file.
Args: Args:
@ -1625,7 +1751,8 @@ def upload(app: App):
file_copies.append( file_copies.append(
UploadFile( UploadFile(
file=content_copy, file=content_copy,
filename=file.filename, path=Path(file.filename.lstrip("/")) if file.filename else None,
_deprecated_filename=file.filename,
size=file.size, size=file.size,
headers=file.headers, headers=file.headers,
) )

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Dict, Iterable, Optional, Tuple, Type, Union from typing import TYPE_CHECKING, Dict, Iterable, Optional, Sequence, Tuple, Type, Union
from reflex import constants from reflex import constants
from reflex.compiler import templates, utils from reflex.compiler import templates, utils
@ -78,6 +78,7 @@ def _compile_app(app_root: Component) -> str:
hooks=app_root._get_all_hooks(), hooks=app_root._get_all_hooks(),
window_libraries=window_libraries, window_libraries=window_libraries,
render=app_root.render(), render=app_root.render(),
dynamic_imports=app_root._get_all_dynamic_imports(),
) )
@ -507,7 +508,7 @@ def compile_tailwind(
The compiled Tailwind config. The compiled Tailwind config.
""" """
# Get the path for the output file. # Get the path for the output file.
output_path = get_web_dir() / constants.Tailwind.CONFIG output_path = str((get_web_dir() / constants.Tailwind.CONFIG).absolute())
# Compile the config. # Compile the config.
code = _compile_tailwind(config) code = _compile_tailwind(config)
@ -544,7 +545,47 @@ def purge_web_pages_dir():
if TYPE_CHECKING: if TYPE_CHECKING:
from reflex.app import UnevaluatedPage from reflex.app import ComponentCallable, UnevaluatedPage
def _into_component_once(component: Component | ComponentCallable) -> Component | None:
"""Convert a component to a Component.
Args:
component: The component to convert.
Returns:
The converted component.
"""
if isinstance(component, Component):
return component
if isinstance(component, (Var, int, float, str)):
return Fragment.create(component)
if isinstance(component, Sequence):
return Fragment.create(*component)
return None
def into_component(component: Component | ComponentCallable) -> Component:
"""Convert a component to a Component.
Args:
component: The component to convert.
Returns:
The converted component.
Raises:
TypeError: If the component is not a Component.
"""
if (converted := _into_component_once(component)) is not None:
return converted
if (
callable(component)
and (converted := _into_component_once(component())) is not None
):
return converted
raise TypeError(f"Expected a Component, got {type(component)}")
def compile_unevaluated_page( def compile_unevaluated_page(
@ -567,12 +608,7 @@ def compile_unevaluated_page(
The compiled component and whether state should be enabled. The compiled component and whether state should be enabled.
""" """
# Generate the component if it is a callable. # Generate the component if it is a callable.
component = page.component component = into_component(page.component)
component = component if isinstance(component, Component) else component()
# unpack components that return tuples in an rx.fragment.
if isinstance(component, tuple):
component = Fragment.create(*component)
component._add_style_recursive(style or {}, theme) component._add_style_recursive(style or {}, theme)
@ -677,10 +713,8 @@ class ExecutorSafeFunctions:
The route, compiled component, and compiled page. The route, compiled component, and compiled page.
""" """
component, enable_state = compile_unevaluated_page( component, enable_state = compile_unevaluated_page(
route, cls.UNCOMPILED_PAGES[route] route, cls.UNCOMPILED_PAGES[route], cls.STATE, style, theme
) )
component = component if isinstance(component, Component) else component()
component._add_style_recursive(style, theme)
return route, component, compile_page(route, component, cls.STATE) return route, component, compile_page(route, component, cls.STATE)
@classmethod @classmethod

View File

@ -48,11 +48,10 @@ class ReflexJinjaEnvironment(Environment):
def __init__(self) -> None: def __init__(self) -> None:
"""Set default environment.""" """Set default environment."""
extensions = ["jinja2.ext.debug"]
super().__init__( super().__init__(
extensions=extensions,
trim_blocks=True, trim_blocks=True,
lstrip_blocks=True, lstrip_blocks=True,
auto_reload=False,
) )
self.filters["json_dumps"] = json_dumps self.filters["json_dumps"] = json_dumps
self.filters["react_setter"] = lambda state: f"set{state.capitalize()}" self.filters["react_setter"] = lambda state: f"set{state.capitalize()}"

View File

@ -119,24 +119,34 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]:
validate_imports(collapsed_import_dict) validate_imports(collapsed_import_dict)
import_dicts = [] import_dicts = []
for lib, fields in collapsed_import_dict.items(): for lib, fields in collapsed_import_dict.items():
default, rest = compile_import_statement(fields)
# prevent lib from being rendered on the page if all imports are non rendered kind # prevent lib from being rendered on the page if all imports are non rendered kind
if not any(f.render for f in fields): if not any(f.render for f in fields):
continue continue
if not lib: lib_paths: dict[str, list[ImportVar]] = {}
if default:
raise ValueError("No default field allowed for empty library.")
if rest is None or len(rest) == 0:
raise ValueError("No fields to import.")
import_dicts.extend(get_import_dict(module) for module in sorted(rest))
continue
# remove the version before rendering the package imports for field in fields:
lib = format.format_library_name(lib) lib_paths.setdefault(field.package_path, []).append(field)
import_dicts.append(get_import_dict(lib, default, rest)) compiled = {
path: compile_import_statement(fields) for path, fields in lib_paths.items()
}
for path, (default, rest) in compiled.items():
if not lib:
if default:
raise ValueError("No default field allowed for empty library.")
if rest is None or len(rest) == 0:
raise ValueError("No fields to import.")
import_dicts.extend(get_import_dict(module) for module in sorted(rest))
continue
# remove the version before rendering the package imports
formatted_lib = format.format_library_name(lib) + (
path if path != "/" else ""
)
import_dicts.append(get_import_dict(formatted_lib, default, rest))
return import_dicts return import_dicts
@ -513,6 +523,8 @@ def write_page(path: str | Path, code: str):
""" """
path = Path(path) path = Path(path)
path_ops.mkdir(path.parent) path_ops.mkdir(path.parent)
if path.exists() and path.read_text(encoding="utf-8") == code:
return
path.write_text(code, encoding="utf-8") path.write_text(code, encoding="utf-8")

View File

@ -7,9 +7,44 @@ from typing import Any, Iterator
from reflex.components.component import Component, LiteralComponentVar from reflex.components.component import Component, LiteralComponentVar
from reflex.components.tags import Tag from reflex.components.tags import Tag
from reflex.components.tags.tagless import Tagless from reflex.components.tags.tagless import Tagless
from reflex.config import PerformanceMode, environment
from reflex.utils import console
from reflex.utils.decorator import once
from reflex.utils.imports import ParsedImportDict from reflex.utils.imports import ParsedImportDict
from reflex.vars import BooleanVar, ObjectVar, Var from reflex.vars import BooleanVar, ObjectVar, Var
from reflex.vars.base import VarData from reflex.vars.base import VarData
from reflex.vars.sequence import LiteralStringVar
@once
def get_performance_mode():
"""Get the performance mode.
Returns:
The performance mode.
"""
return environment.REFLEX_PERF_MODE.get()
def validate_str(value: str):
"""Validate a string value.
Args:
value: The value to validate.
Raises:
ValueError: If the value is a Var and the performance mode is set to raise.
"""
perf_mode = get_performance_mode()
if perf_mode != PerformanceMode.OFF and value.startswith("reflex___state"):
if perf_mode == PerformanceMode.WARN:
console.warn(
f"Output includes {value!s} which will be displayed as a string. If you are calling `str` on a Var, consider using .to_string() instead."
)
elif perf_mode == PerformanceMode.RAISE:
raise ValueError(
f"Output includes {value!s} which will be displayed as a string. If you are calling `str` on a Var, consider using .to_string() instead."
)
class Bare(Component): class Bare(Component):
@ -28,9 +63,14 @@ class Bare(Component):
The component. The component.
""" """
if isinstance(contents, Var): if isinstance(contents, Var):
if isinstance(contents, LiteralStringVar):
validate_str(contents._var_value)
return cls(contents=contents) return cls(contents=contents)
else: else:
if isinstance(contents, str):
validate_str(contents)
contents = str(contents) if contents is not None else "" contents = str(contents) if contents is not None else ""
return cls(contents=contents) return cls(contents=contents)
def _get_all_hooks_internal(self) -> dict[str, VarData | None]: def _get_all_hooks_internal(self) -> dict[str, VarData | None]:

View File

@ -23,6 +23,8 @@ from typing import (
Union, Union,
) )
from typing_extensions import Self
import reflex.state import reflex.state
from reflex.base import Base from reflex.base import Base
from reflex.compiler.templates import STATEFUL_COMPONENT from reflex.compiler.templates import STATEFUL_COMPONENT
@ -49,13 +51,7 @@ from reflex.event import (
) )
from reflex.style import Style, format_as_emotion from reflex.style import Style, format_as_emotion
from reflex.utils import format, imports, types from reflex.utils import format, imports, types
from reflex.utils.imports import ( from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports
ImmutableParsedImportDict,
ImportDict,
ImportVar,
ParsedImportDict,
parse_imports,
)
from reflex.vars import VarData from reflex.vars import VarData
from reflex.vars.base import ( from reflex.vars.base import (
CachedVarOperation, CachedVarOperation,
@ -179,6 +175,7 @@ ComponentStyle = Dict[
Union[str, Type[BaseComponent], Callable, ComponentNamespace], Any Union[str, Type[BaseComponent], Callable, ComponentNamespace], Any
] ]
ComponentChild = Union[types.PrimitiveType, Var, BaseComponent] ComponentChild = Union[types.PrimitiveType, Var, BaseComponent]
ComponentChildTypes = (*types.PrimitiveTypes, Var, BaseComponent)
def satisfies_type_hint(obj: Any, type_hint: Any) -> bool: def satisfies_type_hint(obj: Any, type_hint: Any) -> bool:
@ -191,11 +188,7 @@ def satisfies_type_hint(obj: Any, type_hint: Any) -> bool:
Returns: Returns:
Whether the object satisfies the type hint. Whether the object satisfies the type hint.
""" """
if isinstance(obj, LiteralVar): return types._isinstance(obj, type_hint, nested=1)
return types._isinstance(obj._var_value, type_hint)
if isinstance(obj, Var):
return types._issubclass(obj._var_type, type_hint)
return types._isinstance(obj, type_hint)
class Component(BaseComponent, ABC): class Component(BaseComponent, ABC):
@ -688,7 +681,7 @@ class Component(BaseComponent, ABC):
} }
@classmethod @classmethod
def create(cls, *children, **props) -> Component: def create(cls, *children, **props) -> Self:
"""Create the component. """Create the component.
Args: Args:
@ -712,8 +705,8 @@ class Component(BaseComponent, ABC):
validate_children(child) validate_children(child)
# Make sure the child is a valid type. # Make sure the child is a valid type.
if isinstance(child, dict) or not types._isinstance( if isinstance(child, dict) or not isinstance(
child, ComponentChild child, ComponentChildTypes
): ):
raise ChildrenTypeError(component=cls.__name__, child=child) raise ChildrenTypeError(component=cls.__name__, child=child)
@ -1209,7 +1202,7 @@ class Component(BaseComponent, ABC):
Returns: Returns:
True if the dependency should be transpiled. True if the dependency should be transpiled.
""" """
return ( return bool(self.transpile_packages) and (
dep in self.transpile_packages dep in self.transpile_packages
or format.format_library_name(dep or "") in self.transpile_packages or format.format_library_name(dep or "") in self.transpile_packages
) )
@ -1292,9 +1285,10 @@ class Component(BaseComponent, ABC):
event_imports = Imports.EVENTS if self.event_triggers else {} event_imports = Imports.EVENTS if self.event_triggers else {}
# Collect imports from Vars used directly by this component. # Collect imports from Vars used directly by this component.
var_datas = [var._get_all_var_data() for var in self._get_vars()] var_imports = [
var_imports: List[ImmutableParsedImportDict] = [ var_data.imports
var_data.imports for var_data in var_datas if var_data is not None for var in self._get_vars()
if (var_data := var._get_all_var_data()) is not None
] ]
added_import_dicts: list[ParsedImportDict] = [] added_import_dicts: list[ParsedImportDict] = []
@ -1771,9 +1765,7 @@ class CustomComponent(Component):
return [ return [
Var( Var(
_js_expr=name, _js_expr=name,
_var_type=( _var_type=(prop._var_type if isinstance(prop, Var) else type(prop)),
prop._var_type if types._isinstance(prop, Var) else type(prop)
),
).guess_type() ).guess_type()
for name, prop in self.props.items() for name, prop in self.props.items()
] ]
@ -1795,9 +1787,6 @@ class CustomComponent(Component):
include_children=include_children, ignore_ids=ignore_ids include_children=include_children, ignore_ids=ignore_ids
) )
yield from filter(lambda prop: isinstance(prop, Var), self.props.values()) yield from filter(lambda prop: isinstance(prop, Var), self.props.values())
yield from self.get_component(self)._get_vars(
include_children=include_children, ignore_ids=ignore_ids
)
@lru_cache(maxsize=None) # noqa: B019 @lru_cache(maxsize=None) # noqa: B019
def get_component(self) -> Component: def get_component(self) -> Component:

View File

@ -54,9 +54,10 @@ class Foreach(Component):
TypeError: If the render function is a ComponentState. TypeError: If the render function is a ComponentState.
UntypedVarError: If the iterable is of type Any without a type annotation. UntypedVarError: If the iterable is of type Any without a type annotation.
""" """
from reflex.vars.object import ObjectVar from reflex.vars import ArrayVar, ObjectVar, StringVar
iterable = LiteralVar.create(iterable).guess_type()
iterable = LiteralVar.create(iterable)
if iterable._var_type == Any: if iterable._var_type == Any:
raise ForeachVarError( raise ForeachVarError(
f"Could not foreach over var `{iterable!s}` of type Any. " f"Could not foreach over var `{iterable!s}` of type Any. "
@ -75,6 +76,15 @@ class Foreach(Component):
if isinstance(iterable, ObjectVar): if isinstance(iterable, ObjectVar):
iterable = iterable.entries() iterable = iterable.entries()
if isinstance(iterable, StringVar):
iterable = iterable.split()
if not isinstance(iterable, ArrayVar):
raise ForeachVarError(
f"Could not foreach over var `{iterable!s}` of type {iterable._var_type}. "
"See https://reflex.dev/docs/library/dynamic-rendering/foreach/"
)
component = cls( component = cls(
iterable=iterable, iterable=iterable,
render_fn=render_fn, render_fn=render_fn,

View File

@ -178,9 +178,9 @@ class Match(MemoizationLeaf):
first_case_return = match_cases[0][-1] first_case_return = match_cases[0][-1]
return_type = type(first_case_return) return_type = type(first_case_return)
if types._isinstance(first_case_return, BaseComponent): if isinstance(first_case_return, BaseComponent):
return_type = BaseComponent return_type = BaseComponent
elif types._isinstance(first_case_return, Var): elif isinstance(first_case_return, Var):
return_type = Var return_type = Var
for index, case in enumerate(match_cases): for index, case in enumerate(match_cases):
@ -228,8 +228,8 @@ class Match(MemoizationLeaf):
# Validate the match cases (as well as the default case) to have Var return types. # Validate the match cases (as well as the default case) to have Var return types.
if any( if any(
case for case in match_cases if not types._isinstance(case[-1], Var) case for case in match_cases if not isinstance(case[-1], Var)
) or not types._isinstance(default, Var): ) or not isinstance(default, Var):
raise ValueError("Return types of match cases should be Vars.") raise ValueError("Return types of match cases should be Vars.")
return Var( return Var(

View File

@ -2,14 +2,12 @@
from reflex.components.component import ComponentNamespace from reflex.components.component import ComponentNamespace
from reflex.components.core.colors import color from reflex.components.core.colors import color
from reflex.components.core.cond import color_mode_cond, cond from reflex.components.core.cond import color_mode_cond
from reflex.components.core.responsive import tablet_and_desktop from reflex.components.core.responsive import desktop_only
from reflex.components.el.elements.inline import A from reflex.components.el.elements.inline import A
from reflex.components.el.elements.media import Path, Rect, Svg from reflex.components.el.elements.media import Path, Rect, Svg
from reflex.components.radix.themes.typography.text import Text from reflex.components.radix.themes.typography.text import Text
from reflex.experimental.client_state import ClientStateVar
from reflex.style import Style from reflex.style import Style
from reflex.vars.base import Var, VarData
class StickyLogo(Svg): class StickyLogo(Svg):
@ -87,7 +85,7 @@ class StickyBadge(A):
""" """
return super().create( return super().create(
StickyLogo.create(), StickyLogo.create(),
tablet_and_desktop(StickyLabel.create()), desktop_only(StickyLabel.create()),
href="https://reflex.dev", href="https://reflex.dev",
target="_blank", target="_blank",
width="auto", width="auto",
@ -102,36 +100,12 @@ class StickyBadge(A):
Returns: Returns:
The style of the component. The style of the component.
""" """
is_localhost_cs = ClientStateVar.create(
"is_localhost",
default=True,
global_ref=False,
)
localhost_hostnames = Var.create(
["localhost", "127.0.0.1", "[::1]"]
).guess_type()
is_localhost_expr = localhost_hostnames.contains(
Var("window.location.hostname", _var_type=str).guess_type(),
)
check_is_localhost = Var(
f"useEffect(({is_localhost_cs}) => {is_localhost_cs.set}({is_localhost_expr}), [])",
_var_data=VarData(
imports={"react": "useEffect"},
),
)
is_localhost = is_localhost_cs.value._replace(
merge_var_data=VarData.merge(
check_is_localhost._get_all_var_data(),
VarData(hooks={str(check_is_localhost): None}),
),
)
return Style( return Style(
{ {
"position": "fixed", "position": "fixed",
"bottom": "1rem", "bottom": "1rem",
"right": "1rem", "right": "1rem",
# Do not show the badge on localhost. "display": "flex",
"display": cond(is_localhost, "none", "flex"),
"flex-direction": "row", "flex-direction": "row",
"gap": "0.375rem", "gap": "0.375rem",
"align-items": "center", "align-items": "center",

View File

@ -29,7 +29,7 @@ from reflex.event import (
from reflex.utils import format from reflex.utils import format
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
from reflex.vars import VarData from reflex.vars import VarData
from reflex.vars.base import CallableVar, Var, get_unique_variable_name from reflex.vars.base import Var, get_unique_variable_name
from reflex.vars.sequence import LiteralStringVar from reflex.vars.sequence import LiteralStringVar
DEFAULT_UPLOAD_ID: str = "default" DEFAULT_UPLOAD_ID: str = "default"
@ -45,7 +45,6 @@ upload_files_context_var_data: VarData = VarData(
) )
@CallableVar
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var: def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var:
"""Get the file upload drop trigger. """Get the file upload drop trigger.
@ -75,7 +74,6 @@ def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var:
) )
@CallableVar
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var: def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var:
"""Get the list of selected files. """Get the list of selected files.

View File

@ -13,14 +13,12 @@ from reflex.event import CallableEventSpec, EventSpec, EventType
from reflex.style import Style from reflex.style import Style
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
from reflex.vars import VarData from reflex.vars import VarData
from reflex.vars.base import CallableVar, Var from reflex.vars.base import Var
DEFAULT_UPLOAD_ID: str DEFAULT_UPLOAD_ID: str
upload_files_context_var_data: VarData upload_files_context_var_data: VarData
@CallableVar
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var: ... def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var: ...
@CallableVar
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var: ... def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var: ...
@CallableEventSpec @CallableEventSpec
def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ... def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ...

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import dataclasses import dataclasses
import typing
from typing import ClassVar, Dict, Literal, Optional, Union from typing import ClassVar, Dict, Literal, Optional, Union
from reflex.components.component import Component, ComponentNamespace from reflex.components.component import Component, ComponentNamespace
@ -503,7 +504,7 @@ class CodeBlock(Component, MarkdownComponentMap):
return ["can_copy", "copy_button"] return ["can_copy", "copy_button"]
@classmethod @classmethod
def _get_language_registration_hook(cls, language_var: Var = _LANGUAGE) -> str: def _get_language_registration_hook(cls, language_var: Var = _LANGUAGE) -> Var:
"""Get the hook to register the language. """Get the hook to register the language.
Args: Args:
@ -514,21 +515,46 @@ class CodeBlock(Component, MarkdownComponentMap):
Returns: Returns:
The hook to register the language. The hook to register the language.
""" """
return f""" language_in_there = Var.create(typing.get_args(LiteralCodeLanguage)).contains(
if ({language_var!s}) {{ language_var
(async () => {{ )
try {{ async_load = f"""
(async () => {{
try {{
const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{language_var!s}}}`); const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{language_var!s}}}`);
SyntaxHighlighter.registerLanguage({language_var!s}, module.default); SyntaxHighlighter.registerLanguage({language_var!s}, module.default);
}} catch (error) {{ }} catch (error) {{
console.error(`Error importing language module for ${{{language_var!s}}}:`, error); console.error(`Language ${{{language_var!s}}} is not supported for code blocks inside of markdown: `, error);
}} }}
}})(); }})();
"""
return Var(
f"""
if ({language_var!s}) {{
if (!{language_in_there!s}) {{
console.warn(`Language \\`${{{language_var!s}}}\\` is not supported for code blocks inside of markdown.`);
{language_var!s} = '';
}} else {{
{async_load!s}
}}
}} }}
""" """
if not isinstance(language_var, LiteralVar)
else f"""
if ({language_var!s}) {{
{async_load!s}
}}""",
_var_data=VarData(
imports={
cls.__fields__["library"].default: [
ImportVar(tag="PrismAsyncLight", alias="SyntaxHighlighter")
]
},
),
)
@classmethod @classmethod
def get_component_map_custom_code(cls) -> str: def get_component_map_custom_code(cls) -> Var:
"""Get the custom code for the component. """Get the custom code for the component.
Returns: Returns:

View File

@ -984,7 +984,7 @@ class CodeBlock(Component, MarkdownComponentMap):
def add_style(self): ... def add_style(self): ...
@classmethod @classmethod
def get_component_map_custom_code(cls) -> str: ... def get_component_map_custom_code(cls) -> Var: ...
def add_hooks(self) -> list[str | Var]: ... def add_hooks(self) -> list[str | Var]: ...
class CodeblockNamespace(ComponentNamespace): class CodeblockNamespace(ComponentNamespace):

View File

@ -4,7 +4,7 @@ from reflex.components.component import Component
from reflex.utils import format from reflex.utils import format
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
from reflex.vars.base import LiteralVar, Var from reflex.vars.base import LiteralVar, Var
from reflex.vars.sequence import LiteralStringVar from reflex.vars.sequence import LiteralStringVar, StringVar
class LucideIconComponent(Component): class LucideIconComponent(Component):
@ -40,7 +40,12 @@ class Icon(LucideIconComponent):
The created component. The created component.
""" """
if children: if children:
if len(children) == 1 and isinstance(children[0], str): if len(children) == 1:
child = Var.create(children[0]).guess_type()
if not isinstance(child, StringVar):
raise AttributeError(
f"Icon name must be a string, got {children[0]._var_type if isinstance(children[0], Var) else children[0]}"
)
props["tag"] = children[0] props["tag"] = children[0]
else: else:
raise AttributeError( raise AttributeError(
@ -49,22 +54,33 @@ class Icon(LucideIconComponent):
if "tag" not in props: if "tag" not in props:
raise AttributeError("Missing 'tag' keyword-argument for Icon") raise AttributeError("Missing 'tag' keyword-argument for Icon")
tag: str | Var | LiteralVar = props.pop("tag") tag: str | Var | LiteralVar = Var.create(props.pop("tag"))
if isinstance(tag, LiteralVar): if isinstance(tag, LiteralVar):
if isinstance(tag, LiteralStringVar): if isinstance(tag, LiteralStringVar):
tag = tag._var_value tag = tag._var_value
else: else:
raise TypeError(f"Icon name must be a string, got {type(tag)}") raise TypeError(f"Icon name must be a string, got {type(tag)}")
elif isinstance(tag, Var): elif isinstance(tag, Var):
return DynamicIcon.create(name=tag, **props) tag_stringified = tag.guess_type()
if not isinstance(tag_stringified, StringVar):
raise TypeError(f"Icon name must be a string, got {tag._var_type}")
return DynamicIcon.create(name=tag_stringified.replace("_", "-"), **props)
if ( if (
not isinstance(tag, str) not isinstance(tag, str)
or format.to_snake_case(tag) not in LUCIDE_ICON_LIST or format.to_snake_case(tag) not in LUCIDE_ICON_LIST
): ):
if isinstance(tag, str):
icons_sorted = sorted(
LUCIDE_ICON_LIST,
key=lambda s: format.length_of_largest_common_substring(tag, s),
reverse=True,
)
else:
icons_sorted = LUCIDE_ICON_LIST
raise ValueError( raise ValueError(
f"Invalid icon tag: {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(icons_sorted[0:25])}, ..."
"\nSee full list at https://lucide.dev/icons." "\nSee full list at https://reflex.dev/docs/library/data-display/icon/#icons-list."
) )
if tag in LUCIDE_ICON_MAPPING_OVERRIDE: if tag in LUCIDE_ICON_MAPPING_OVERRIDE:

View File

@ -6,13 +6,12 @@ import dataclasses
import textwrap import textwrap
from functools import lru_cache from functools import lru_cache
from hashlib import md5 from hashlib import md5
from typing import Any, Callable, Dict, Sequence, Union from typing import Any, Callable, Dict, Sequence
from reflex.components.component import BaseComponent, Component, CustomComponent from reflex.components.component import BaseComponent, Component, CustomComponent
from reflex.components.tags.tag import Tag from reflex.components.tags.tag import Tag
from reflex.utils import types
from reflex.utils.imports import ImportDict, ImportVar from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars.base import LiteralVar, Var from reflex.vars.base import LiteralVar, Var, VarData
from reflex.vars.function import ARRAY_ISARRAY, ArgsFunctionOperation, DestructuredArg from reflex.vars.function import ARRAY_ISARRAY, ArgsFunctionOperation, DestructuredArg
from reflex.vars.number import ternary_operation from reflex.vars.number import ternary_operation
@ -83,13 +82,13 @@ class MarkdownComponentMap:
_explicit_return: bool = dataclasses.field(default=False) _explicit_return: bool = dataclasses.field(default=False)
@classmethod @classmethod
def get_component_map_custom_code(cls) -> str: def get_component_map_custom_code(cls) -> Var:
"""Get the custom code for the component map. """Get the custom code for the component map.
Returns: Returns:
The custom code for the component map. The custom code for the component map.
""" """
return "" return Var("")
@classmethod @classmethod
def create_map_fn_var( def create_map_fn_var(
@ -97,6 +96,7 @@ class MarkdownComponentMap:
fn_body: Var | None = None, fn_body: Var | None = None,
fn_args: Sequence[str] | None = None, fn_args: Sequence[str] | None = None,
explicit_return: bool | None = None, explicit_return: bool | None = None,
var_data: VarData | None = None,
) -> Var: ) -> Var:
"""Create a function Var for the component map. """Create a function Var for the component map.
@ -104,6 +104,7 @@ class MarkdownComponentMap:
fn_body: The formatted component as a string. fn_body: The formatted component as a string.
fn_args: The function arguments. fn_args: The function arguments.
explicit_return: Whether to use explicit return syntax. explicit_return: Whether to use explicit return syntax.
var_data: The var data for the function.
Returns: Returns:
The function Var for the component map. The function Var for the component map.
@ -116,6 +117,7 @@ class MarkdownComponentMap:
args_names=(DestructuredArg(fields=tuple(fn_args)),), args_names=(DestructuredArg(fields=tuple(fn_args)),),
return_expr=fn_body, return_expr=fn_body,
explicit_return=explicit_return, explicit_return=explicit_return,
_var_data=var_data,
) )
@classmethod @classmethod
@ -166,7 +168,7 @@ class Markdown(Component):
Returns: Returns:
The markdown component. The markdown component.
""" """
if len(children) != 1 or not types._isinstance(children[0], Union[str, Var]): if len(children) != 1 or not isinstance(children[0], (str, Var)):
raise ValueError( raise ValueError(
"Markdown component must have exactly one child containing the markdown source." "Markdown component must have exactly one child containing the markdown source."
) )
@ -239,6 +241,15 @@ class Markdown(Component):
component(_MOCK_ARG)._get_all_imports() component(_MOCK_ARG)._get_all_imports()
for component in self.component_map.values() for component in self.component_map.values()
], ],
*(
[inline_code_var_data.old_school_imports()]
if (
inline_code_var_data
:= self._get_inline_code_fn_var()._get_all_var_data()
)
is not None
else []
),
] ]
def _get_tag_map_fn_var(self, tag: str) -> Var: def _get_tag_map_fn_var(self, tag: str) -> Var:
@ -278,12 +289,20 @@ class Markdown(Component):
self._get_map_fn_custom_code_from_children(self.get_component("code")) self._get_map_fn_custom_code_from_children(self.get_component("code"))
) )
codeblock_custom_code = "\n".join(custom_code_list) var_data = VarData.merge(
*[
code._get_all_var_data()
for code in custom_code_list
if isinstance(code, Var)
]
)
codeblock_custom_code = "\n".join(map(str, custom_code_list))
# Format the code to handle inline and block code. # Format the code to handle inline and block code.
formatted_code = f""" formatted_code = f"""
const match = (className || '').match(/language-(?<lang>.*)/); const match = (className || '').match(/language-(?<lang>.*)/);
const {_LANGUAGE!s} = match ? match[1] : ''; let {_LANGUAGE!s} = match ? match[1] : '';
{codeblock_custom_code}; {codeblock_custom_code};
return inline ? ( return inline ? (
{self.format_component("code")} {self.format_component("code")}
@ -302,6 +321,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
), ),
fn_body=Var(_js_expr=formatted_code), fn_body=Var(_js_expr=formatted_code),
explicit_return=True, explicit_return=True,
var_data=var_data,
) )
def get_component(self, tag: str, **props) -> Component: def get_component(self, tag: str, **props) -> Component:
@ -381,7 +401,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
def _get_map_fn_custom_code_from_children( def _get_map_fn_custom_code_from_children(
self, component: BaseComponent self, component: BaseComponent
) -> list[str]: ) -> list[str | Var]:
"""Recursively get markdown custom code from children components. """Recursively get markdown custom code from children components.
Args: Args:
@ -390,7 +410,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
Returns: Returns:
A list of markdown custom code strings. A list of markdown custom code strings.
""" """
custom_code_list = [] custom_code_list: list[str | Var] = []
if isinstance(component, MarkdownComponentMap): if isinstance(component, MarkdownComponentMap):
custom_code_list.append(component.get_component_map_custom_code()) custom_code_list.append(component.get_component_map_custom_code())

View File

@ -11,7 +11,7 @@ from reflex.components.component import Component
from reflex.event import EventType from reflex.event import EventType
from reflex.style import Style from reflex.style import Style
from reflex.utils.imports import ImportDict from reflex.utils.imports import ImportDict
from reflex.vars.base import LiteralVar, Var from reflex.vars.base import LiteralVar, Var, VarData
_CHILDREN = Var(_js_expr="children", _var_type=str) _CHILDREN = Var(_js_expr="children", _var_type=str)
_PROPS = Var(_js_expr="...props") _PROPS = Var(_js_expr="...props")
@ -32,13 +32,14 @@ def get_base_component_map() -> dict[str, Callable]: ...
@dataclasses.dataclass() @dataclasses.dataclass()
class MarkdownComponentMap: class MarkdownComponentMap:
@classmethod @classmethod
def get_component_map_custom_code(cls) -> str: ... def get_component_map_custom_code(cls) -> Var: ...
@classmethod @classmethod
def create_map_fn_var( def create_map_fn_var(
cls, cls,
fn_body: Var | None = None, fn_body: Var | None = None,
fn_args: Sequence[str] | None = None, fn_args: Sequence[str] | None = None,
explicit_return: bool | None = None, explicit_return: bool | None = None,
var_data: VarData | None = None,
) -> Var: ... ) -> Var: ...
@classmethod @classmethod
def get_fn_args(cls) -> Sequence[str]: ... def get_fn_args(cls) -> Sequence[str]: ...

View File

@ -1,5 +1,32 @@
"""Plotly components.""" """Plotly components."""
from .plotly import Plotly from reflex.components.component import ComponentNamespace
plotly = Plotly.create from .plotly import (
Plotly,
PlotlyBasic,
PlotlyCartesian,
PlotlyFinance,
PlotlyGeo,
PlotlyGl2d,
PlotlyGl3d,
PlotlyMapbox,
PlotlyStrict,
)
class PlotlyNamespace(ComponentNamespace):
"""Plotly namespace."""
__call__ = Plotly.create
basic = PlotlyBasic.create
cartesian = PlotlyCartesian.create
geo = PlotlyGeo.create
gl2d = PlotlyGl2d.create
gl3d = PlotlyGl3d.create
finance = PlotlyFinance.create
mapbox = PlotlyMapbox.create
strict = PlotlyStrict.create
plotly = PlotlyNamespace()

View File

@ -10,6 +10,7 @@ from reflex.components.component import Component, NoSSRComponent
from reflex.components.core.cond import color_mode_cond from reflex.components.core.cond import color_mode_cond
from reflex.event import EventHandler, no_args_event_spec from reflex.event import EventHandler, no_args_event_spec
from reflex.utils import console from reflex.utils import console
from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars.base import LiteralVar, Var from reflex.vars.base import LiteralVar, Var
try: try:
@ -278,3 +279,237 @@ const extractPoints = (points) => {
# Spread the figure dict over props, nothing to merge. # Spread the figure dict over props, nothing to merge.
tag.special_props.append(Var(_js_expr=f"{{...{figure!s}}}")) tag.special_props.append(Var(_js_expr=f"{{...{figure!s}}}"))
return tag return tag
CREATE_PLOTLY_COMPONENT: ImportDict = {
"react-plotly.js": [
ImportVar(
tag="createPlotlyComponent",
is_default=True,
package_path="/factory",
),
]
}
def dynamic_plotly_import(name: str, package: str) -> str:
"""Create a dynamic import for a plotly component.
Args:
name: The name of the component.
package: The package path of the component.
Returns:
The dynamic import for the plotly component.
"""
return f"""
const {name} = dynamic(() => import('{package}').then(mod => createPlotlyComponent(mod)), {{ssr: false}})
"""
class PlotlyBasic(Plotly):
"""Display a basic plotly graph."""
tag: str = "BasicPlotlyPlot"
library = "react-plotly.js@2.6.0"
lib_dependencies: list[str] = ["plotly.js-basic-dist-min@3.0.0"]
def add_imports(self) -> ImportDict | list[ImportDict]:
"""Add imports for the plotly basic component.
Returns:
The imports for the plotly basic component.
"""
return CREATE_PLOTLY_COMPONENT
def _get_dynamic_imports(self) -> str:
"""Get the dynamic imports for the plotly basic component.
Returns:
The dynamic imports for the plotly basic component.
"""
return dynamic_plotly_import(self.tag, "plotly.js-basic-dist-min")
class PlotlyCartesian(Plotly):
"""Display a plotly cartesian graph."""
tag: str = "CartesianPlotlyPlot"
library = "react-plotly.js@2.6.0"
lib_dependencies: list[str] = ["plotly.js-cartesian-dist-min@3.0.0"]
def add_imports(self) -> ImportDict | list[ImportDict]:
"""Add imports for the plotly cartesian component.
Returns:
The imports for the plotly cartesian component.
"""
return CREATE_PLOTLY_COMPONENT
def _get_dynamic_imports(self) -> str:
"""Get the dynamic imports for the plotly cartesian component.
Returns:
The dynamic imports for the plotly cartesian component.
"""
return dynamic_plotly_import(self.tag, "plotly.js-cartesian-dist-min")
class PlotlyGeo(Plotly):
"""Display a plotly geo graph."""
tag: str = "GeoPlotlyPlot"
library = "react-plotly.js@2.6.0"
lib_dependencies: list[str] = ["plotly.js-geo-dist-min@3.0.0"]
def add_imports(self) -> ImportDict | list[ImportDict]:
"""Add imports for the plotly geo component.
Returns:
The imports for the plotly geo component.
"""
return CREATE_PLOTLY_COMPONENT
def _get_dynamic_imports(self) -> str:
"""Get the dynamic imports for the plotly geo component.
Returns:
The dynamic imports for the plotly geo component.
"""
return dynamic_plotly_import(self.tag, "plotly.js-geo-dist-min")
class PlotlyGl3d(Plotly):
"""Display a plotly 3d graph."""
tag: str = "Gl3dPlotlyPlot"
library = "react-plotly.js@2.6.0"
lib_dependencies: list[str] = ["plotly.js-gl3d-dist-min@3.0.0"]
def add_imports(self) -> ImportDict | list[ImportDict]:
"""Add imports for the plotly 3d component.
Returns:
The imports for the plotly 3d component.
"""
return CREATE_PLOTLY_COMPONENT
def _get_dynamic_imports(self) -> str:
"""Get the dynamic imports for the plotly 3d component.
Returns:
The dynamic imports for the plotly 3d component.
"""
return dynamic_plotly_import(self.tag, "plotly.js-gl3d-dist-min")
class PlotlyGl2d(Plotly):
"""Display a plotly 2d graph."""
tag: str = "Gl2dPlotlyPlot"
library = "react-plotly.js@2.6.0"
lib_dependencies: list[str] = ["plotly.js-gl2d-dist-min@3.0.0"]
def add_imports(self) -> ImportDict | list[ImportDict]:
"""Add imports for the plotly 2d component.
Returns:
The imports for the plotly 2d component.
"""
return CREATE_PLOTLY_COMPONENT
def _get_dynamic_imports(self) -> str:
"""Get the dynamic imports for the plotly 2d component.
Returns:
The dynamic imports for the plotly 2d component.
"""
return dynamic_plotly_import(self.tag, "plotly.js-gl2d-dist-min")
class PlotlyMapbox(Plotly):
"""Display a plotly mapbox graph."""
tag: str = "MapboxPlotlyPlot"
library = "react-plotly.js@2.6.0"
lib_dependencies: list[str] = ["plotly.js-mapbox-dist-min@3.0.0"]
def add_imports(self) -> ImportDict | list[ImportDict]:
"""Add imports for the plotly mapbox component.
Returns:
The imports for the plotly mapbox component.
"""
return CREATE_PLOTLY_COMPONENT
def _get_dynamic_imports(self) -> str:
"""Get the dynamic imports for the plotly mapbox component.
Returns:
The dynamic imports for the plotly mapbox component.
"""
return dynamic_plotly_import(self.tag, "plotly.js-mapbox-dist-min")
class PlotlyFinance(Plotly):
"""Display a plotly finance graph."""
tag: str = "FinancePlotlyPlot"
library = "react-plotly.js@2.6.0"
lib_dependencies: list[str] = ["plotly.js-finance-dist-min@3.0.0"]
def add_imports(self) -> ImportDict | list[ImportDict]:
"""Add imports for the plotly finance component.
Returns:
The imports for the plotly finance component.
"""
return CREATE_PLOTLY_COMPONENT
def _get_dynamic_imports(self) -> str:
"""Get the dynamic imports for the plotly finance component.
Returns:
The dynamic imports for the plotly finance component.
"""
return dynamic_plotly_import(self.tag, "plotly.js-finance-dist-min")
class PlotlyStrict(Plotly):
"""Display a plotly strict graph."""
tag: str = "StrictPlotlyPlot"
library = "react-plotly.js@2.6.0"
lib_dependencies: list[str] = ["plotly.js-strict-dist-min@3.0.0"]
def add_imports(self) -> ImportDict | list[ImportDict]:
"""Add imports for the plotly strict component.
Returns:
The imports for the plotly strict component.
"""
return CREATE_PLOTLY_COMPONENT
def _get_dynamic_imports(self) -> str:
"""Get the dynamic imports for the plotly strict component.
Returns:
The dynamic imports for the plotly strict component.
"""
return dynamic_plotly_import(self.tag, "plotly.js-strict-dist-min")

View File

@ -11,6 +11,7 @@ from reflex.components.component import NoSSRComponent
from reflex.event import EventType from reflex.event import EventType
from reflex.style import Style from reflex.style import Style
from reflex.utils import console from reflex.utils import console
from reflex.utils.imports import ImportDict
from reflex.vars.base import Var from reflex.vars.base import Var
try: try:
@ -141,3 +142,767 @@ class Plotly(NoSSRComponent):
The Plotly component. The Plotly component.
""" """
... ...
CREATE_PLOTLY_COMPONENT: ImportDict
def dynamic_plotly_import(name: str, package: str) -> str: ...
class PlotlyBasic(Plotly):
def add_imports(self) -> ImportDict | list[ImportDict]: ...
@overload
@classmethod
def create( # type: ignore
cls,
*children,
data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore
layout: Optional[Union[Dict, Var[Dict]]] = None,
template: Optional[Union[Template, Var[Template]]] = None, # type: ignore
config: Optional[Union[Dict, Var[Dict]]] = None,
use_resize_handler: Optional[Union[Var[bool], bool]] = 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_after_plot: Optional[EventType[()]] = None,
on_animated: Optional[EventType[()]] = None,
on_animating_frame: Optional[EventType[()]] = None,
on_animation_interrupted: Optional[EventType[()]] = None,
on_autosize: Optional[EventType[()]] = None,
on_before_hover: Optional[EventType[()]] = None,
on_blur: Optional[EventType[()]] = None,
on_button_clicked: Optional[EventType[()]] = None,
on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_context_menu: Optional[EventType[()]] = None,
on_deselect: Optional[EventType[()]] = None,
on_double_click: Optional[EventType[()]] = None,
on_focus: Optional[EventType[()]] = None,
on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_mount: Optional[EventType[()]] = None,
on_mouse_down: Optional[EventType[()]] = None,
on_mouse_enter: Optional[EventType[()]] = None,
on_mouse_leave: Optional[EventType[()]] = None,
on_mouse_move: Optional[EventType[()]] = None,
on_mouse_out: Optional[EventType[()]] = None,
on_mouse_over: Optional[EventType[()]] = None,
on_mouse_up: Optional[EventType[()]] = None,
on_redraw: Optional[EventType[()]] = None,
on_relayout: Optional[EventType[()]] = None,
on_relayouting: Optional[EventType[()]] = None,
on_restyle: Optional[EventType[()]] = None,
on_scroll: Optional[EventType[()]] = None,
on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_transition_interrupted: Optional[EventType[()]] = None,
on_transitioning: Optional[EventType[()]] = None,
on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_unmount: Optional[EventType[()]] = None,
**props,
) -> "PlotlyBasic":
"""Create the Plotly component.
Args:
*children: The children of the component.
data: The figure to display. This can be a plotly figure or a plotly data json.
layout: The layout of the graph.
template: The template for visual appearance of the graph.
config: The config of the graph.
use_resize_handler: If true, the graph will resize when the window is resized.
on_after_plot: Fired after the plot is redrawn.
on_animated: Fired after the plot was animated.
on_animating_frame: Fired while animating a single frame (does not currently pass data through).
on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example).
on_autosize: Fired when the plot is responsively sized.
on_before_hover: Fired whenever mouse moves over a plot.
on_button_clicked: Fired when a plotly UI button is clicked.
on_click: Fired when the plot is clicked.
on_deselect: Fired when a selection is cleared (via double click).
on_double_click: Fired when the plot is double clicked.
on_hover: Fired when a plot element is hovered over.
on_relayout: Fired after the plot is laid out (zoom, pan, etc).
on_relayouting: Fired while the plot is being laid out.
on_restyle: Fired after the plot style is changed.
on_redraw: Fired after the plot is redrawn.
on_selected: Fired after selecting plot elements.
on_selecting: Fired while dragging a selection.
on_transitioning: Fired while an animation is occurring.
on_transition_interrupted: Fired when a transition is stopped early.
on_unhover: Fired when a hovered element is no longer hovered.
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
class_name: The class name for the component.
autofocus: Whether the component should take the focus once the page is loaded
custom_attrs: custom attribute
**props: The properties of the component.
Returns:
The Plotly component.
"""
...
class PlotlyCartesian(Plotly):
def add_imports(self) -> ImportDict | list[ImportDict]: ...
@overload
@classmethod
def create( # type: ignore
cls,
*children,
data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore
layout: Optional[Union[Dict, Var[Dict]]] = None,
template: Optional[Union[Template, Var[Template]]] = None, # type: ignore
config: Optional[Union[Dict, Var[Dict]]] = None,
use_resize_handler: Optional[Union[Var[bool], bool]] = 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_after_plot: Optional[EventType[()]] = None,
on_animated: Optional[EventType[()]] = None,
on_animating_frame: Optional[EventType[()]] = None,
on_animation_interrupted: Optional[EventType[()]] = None,
on_autosize: Optional[EventType[()]] = None,
on_before_hover: Optional[EventType[()]] = None,
on_blur: Optional[EventType[()]] = None,
on_button_clicked: Optional[EventType[()]] = None,
on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_context_menu: Optional[EventType[()]] = None,
on_deselect: Optional[EventType[()]] = None,
on_double_click: Optional[EventType[()]] = None,
on_focus: Optional[EventType[()]] = None,
on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_mount: Optional[EventType[()]] = None,
on_mouse_down: Optional[EventType[()]] = None,
on_mouse_enter: Optional[EventType[()]] = None,
on_mouse_leave: Optional[EventType[()]] = None,
on_mouse_move: Optional[EventType[()]] = None,
on_mouse_out: Optional[EventType[()]] = None,
on_mouse_over: Optional[EventType[()]] = None,
on_mouse_up: Optional[EventType[()]] = None,
on_redraw: Optional[EventType[()]] = None,
on_relayout: Optional[EventType[()]] = None,
on_relayouting: Optional[EventType[()]] = None,
on_restyle: Optional[EventType[()]] = None,
on_scroll: Optional[EventType[()]] = None,
on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_transition_interrupted: Optional[EventType[()]] = None,
on_transitioning: Optional[EventType[()]] = None,
on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_unmount: Optional[EventType[()]] = None,
**props,
) -> "PlotlyCartesian":
"""Create the Plotly component.
Args:
*children: The children of the component.
data: The figure to display. This can be a plotly figure or a plotly data json.
layout: The layout of the graph.
template: The template for visual appearance of the graph.
config: The config of the graph.
use_resize_handler: If true, the graph will resize when the window is resized.
on_after_plot: Fired after the plot is redrawn.
on_animated: Fired after the plot was animated.
on_animating_frame: Fired while animating a single frame (does not currently pass data through).
on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example).
on_autosize: Fired when the plot is responsively sized.
on_before_hover: Fired whenever mouse moves over a plot.
on_button_clicked: Fired when a plotly UI button is clicked.
on_click: Fired when the plot is clicked.
on_deselect: Fired when a selection is cleared (via double click).
on_double_click: Fired when the plot is double clicked.
on_hover: Fired when a plot element is hovered over.
on_relayout: Fired after the plot is laid out (zoom, pan, etc).
on_relayouting: Fired while the plot is being laid out.
on_restyle: Fired after the plot style is changed.
on_redraw: Fired after the plot is redrawn.
on_selected: Fired after selecting plot elements.
on_selecting: Fired while dragging a selection.
on_transitioning: Fired while an animation is occurring.
on_transition_interrupted: Fired when a transition is stopped early.
on_unhover: Fired when a hovered element is no longer hovered.
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
class_name: The class name for the component.
autofocus: Whether the component should take the focus once the page is loaded
custom_attrs: custom attribute
**props: The properties of the component.
Returns:
The Plotly component.
"""
...
class PlotlyGeo(Plotly):
def add_imports(self) -> ImportDict | list[ImportDict]: ...
@overload
@classmethod
def create( # type: ignore
cls,
*children,
data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore
layout: Optional[Union[Dict, Var[Dict]]] = None,
template: Optional[Union[Template, Var[Template]]] = None, # type: ignore
config: Optional[Union[Dict, Var[Dict]]] = None,
use_resize_handler: Optional[Union[Var[bool], bool]] = 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_after_plot: Optional[EventType[()]] = None,
on_animated: Optional[EventType[()]] = None,
on_animating_frame: Optional[EventType[()]] = None,
on_animation_interrupted: Optional[EventType[()]] = None,
on_autosize: Optional[EventType[()]] = None,
on_before_hover: Optional[EventType[()]] = None,
on_blur: Optional[EventType[()]] = None,
on_button_clicked: Optional[EventType[()]] = None,
on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_context_menu: Optional[EventType[()]] = None,
on_deselect: Optional[EventType[()]] = None,
on_double_click: Optional[EventType[()]] = None,
on_focus: Optional[EventType[()]] = None,
on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_mount: Optional[EventType[()]] = None,
on_mouse_down: Optional[EventType[()]] = None,
on_mouse_enter: Optional[EventType[()]] = None,
on_mouse_leave: Optional[EventType[()]] = None,
on_mouse_move: Optional[EventType[()]] = None,
on_mouse_out: Optional[EventType[()]] = None,
on_mouse_over: Optional[EventType[()]] = None,
on_mouse_up: Optional[EventType[()]] = None,
on_redraw: Optional[EventType[()]] = None,
on_relayout: Optional[EventType[()]] = None,
on_relayouting: Optional[EventType[()]] = None,
on_restyle: Optional[EventType[()]] = None,
on_scroll: Optional[EventType[()]] = None,
on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_transition_interrupted: Optional[EventType[()]] = None,
on_transitioning: Optional[EventType[()]] = None,
on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_unmount: Optional[EventType[()]] = None,
**props,
) -> "PlotlyGeo":
"""Create the Plotly component.
Args:
*children: The children of the component.
data: The figure to display. This can be a plotly figure or a plotly data json.
layout: The layout of the graph.
template: The template for visual appearance of the graph.
config: The config of the graph.
use_resize_handler: If true, the graph will resize when the window is resized.
on_after_plot: Fired after the plot is redrawn.
on_animated: Fired after the plot was animated.
on_animating_frame: Fired while animating a single frame (does not currently pass data through).
on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example).
on_autosize: Fired when the plot is responsively sized.
on_before_hover: Fired whenever mouse moves over a plot.
on_button_clicked: Fired when a plotly UI button is clicked.
on_click: Fired when the plot is clicked.
on_deselect: Fired when a selection is cleared (via double click).
on_double_click: Fired when the plot is double clicked.
on_hover: Fired when a plot element is hovered over.
on_relayout: Fired after the plot is laid out (zoom, pan, etc).
on_relayouting: Fired while the plot is being laid out.
on_restyle: Fired after the plot style is changed.
on_redraw: Fired after the plot is redrawn.
on_selected: Fired after selecting plot elements.
on_selecting: Fired while dragging a selection.
on_transitioning: Fired while an animation is occurring.
on_transition_interrupted: Fired when a transition is stopped early.
on_unhover: Fired when a hovered element is no longer hovered.
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
class_name: The class name for the component.
autofocus: Whether the component should take the focus once the page is loaded
custom_attrs: custom attribute
**props: The properties of the component.
Returns:
The Plotly component.
"""
...
class PlotlyGl3d(Plotly):
def add_imports(self) -> ImportDict | list[ImportDict]: ...
@overload
@classmethod
def create( # type: ignore
cls,
*children,
data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore
layout: Optional[Union[Dict, Var[Dict]]] = None,
template: Optional[Union[Template, Var[Template]]] = None, # type: ignore
config: Optional[Union[Dict, Var[Dict]]] = None,
use_resize_handler: Optional[Union[Var[bool], bool]] = 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_after_plot: Optional[EventType[()]] = None,
on_animated: Optional[EventType[()]] = None,
on_animating_frame: Optional[EventType[()]] = None,
on_animation_interrupted: Optional[EventType[()]] = None,
on_autosize: Optional[EventType[()]] = None,
on_before_hover: Optional[EventType[()]] = None,
on_blur: Optional[EventType[()]] = None,
on_button_clicked: Optional[EventType[()]] = None,
on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_context_menu: Optional[EventType[()]] = None,
on_deselect: Optional[EventType[()]] = None,
on_double_click: Optional[EventType[()]] = None,
on_focus: Optional[EventType[()]] = None,
on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_mount: Optional[EventType[()]] = None,
on_mouse_down: Optional[EventType[()]] = None,
on_mouse_enter: Optional[EventType[()]] = None,
on_mouse_leave: Optional[EventType[()]] = None,
on_mouse_move: Optional[EventType[()]] = None,
on_mouse_out: Optional[EventType[()]] = None,
on_mouse_over: Optional[EventType[()]] = None,
on_mouse_up: Optional[EventType[()]] = None,
on_redraw: Optional[EventType[()]] = None,
on_relayout: Optional[EventType[()]] = None,
on_relayouting: Optional[EventType[()]] = None,
on_restyle: Optional[EventType[()]] = None,
on_scroll: Optional[EventType[()]] = None,
on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_transition_interrupted: Optional[EventType[()]] = None,
on_transitioning: Optional[EventType[()]] = None,
on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_unmount: Optional[EventType[()]] = None,
**props,
) -> "PlotlyGl3d":
"""Create the Plotly component.
Args:
*children: The children of the component.
data: The figure to display. This can be a plotly figure or a plotly data json.
layout: The layout of the graph.
template: The template for visual appearance of the graph.
config: The config of the graph.
use_resize_handler: If true, the graph will resize when the window is resized.
on_after_plot: Fired after the plot is redrawn.
on_animated: Fired after the plot was animated.
on_animating_frame: Fired while animating a single frame (does not currently pass data through).
on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example).
on_autosize: Fired when the plot is responsively sized.
on_before_hover: Fired whenever mouse moves over a plot.
on_button_clicked: Fired when a plotly UI button is clicked.
on_click: Fired when the plot is clicked.
on_deselect: Fired when a selection is cleared (via double click).
on_double_click: Fired when the plot is double clicked.
on_hover: Fired when a plot element is hovered over.
on_relayout: Fired after the plot is laid out (zoom, pan, etc).
on_relayouting: Fired while the plot is being laid out.
on_restyle: Fired after the plot style is changed.
on_redraw: Fired after the plot is redrawn.
on_selected: Fired after selecting plot elements.
on_selecting: Fired while dragging a selection.
on_transitioning: Fired while an animation is occurring.
on_transition_interrupted: Fired when a transition is stopped early.
on_unhover: Fired when a hovered element is no longer hovered.
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
class_name: The class name for the component.
autofocus: Whether the component should take the focus once the page is loaded
custom_attrs: custom attribute
**props: The properties of the component.
Returns:
The Plotly component.
"""
...
class PlotlyGl2d(Plotly):
def add_imports(self) -> ImportDict | list[ImportDict]: ...
@overload
@classmethod
def create( # type: ignore
cls,
*children,
data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore
layout: Optional[Union[Dict, Var[Dict]]] = None,
template: Optional[Union[Template, Var[Template]]] = None, # type: ignore
config: Optional[Union[Dict, Var[Dict]]] = None,
use_resize_handler: Optional[Union[Var[bool], bool]] = 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_after_plot: Optional[EventType[()]] = None,
on_animated: Optional[EventType[()]] = None,
on_animating_frame: Optional[EventType[()]] = None,
on_animation_interrupted: Optional[EventType[()]] = None,
on_autosize: Optional[EventType[()]] = None,
on_before_hover: Optional[EventType[()]] = None,
on_blur: Optional[EventType[()]] = None,
on_button_clicked: Optional[EventType[()]] = None,
on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_context_menu: Optional[EventType[()]] = None,
on_deselect: Optional[EventType[()]] = None,
on_double_click: Optional[EventType[()]] = None,
on_focus: Optional[EventType[()]] = None,
on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_mount: Optional[EventType[()]] = None,
on_mouse_down: Optional[EventType[()]] = None,
on_mouse_enter: Optional[EventType[()]] = None,
on_mouse_leave: Optional[EventType[()]] = None,
on_mouse_move: Optional[EventType[()]] = None,
on_mouse_out: Optional[EventType[()]] = None,
on_mouse_over: Optional[EventType[()]] = None,
on_mouse_up: Optional[EventType[()]] = None,
on_redraw: Optional[EventType[()]] = None,
on_relayout: Optional[EventType[()]] = None,
on_relayouting: Optional[EventType[()]] = None,
on_restyle: Optional[EventType[()]] = None,
on_scroll: Optional[EventType[()]] = None,
on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_transition_interrupted: Optional[EventType[()]] = None,
on_transitioning: Optional[EventType[()]] = None,
on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_unmount: Optional[EventType[()]] = None,
**props,
) -> "PlotlyGl2d":
"""Create the Plotly component.
Args:
*children: The children of the component.
data: The figure to display. This can be a plotly figure or a plotly data json.
layout: The layout of the graph.
template: The template for visual appearance of the graph.
config: The config of the graph.
use_resize_handler: If true, the graph will resize when the window is resized.
on_after_plot: Fired after the plot is redrawn.
on_animated: Fired after the plot was animated.
on_animating_frame: Fired while animating a single frame (does not currently pass data through).
on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example).
on_autosize: Fired when the plot is responsively sized.
on_before_hover: Fired whenever mouse moves over a plot.
on_button_clicked: Fired when a plotly UI button is clicked.
on_click: Fired when the plot is clicked.
on_deselect: Fired when a selection is cleared (via double click).
on_double_click: Fired when the plot is double clicked.
on_hover: Fired when a plot element is hovered over.
on_relayout: Fired after the plot is laid out (zoom, pan, etc).
on_relayouting: Fired while the plot is being laid out.
on_restyle: Fired after the plot style is changed.
on_redraw: Fired after the plot is redrawn.
on_selected: Fired after selecting plot elements.
on_selecting: Fired while dragging a selection.
on_transitioning: Fired while an animation is occurring.
on_transition_interrupted: Fired when a transition is stopped early.
on_unhover: Fired when a hovered element is no longer hovered.
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
class_name: The class name for the component.
autofocus: Whether the component should take the focus once the page is loaded
custom_attrs: custom attribute
**props: The properties of the component.
Returns:
The Plotly component.
"""
...
class PlotlyMapbox(Plotly):
def add_imports(self) -> ImportDict | list[ImportDict]: ...
@overload
@classmethod
def create( # type: ignore
cls,
*children,
data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore
layout: Optional[Union[Dict, Var[Dict]]] = None,
template: Optional[Union[Template, Var[Template]]] = None, # type: ignore
config: Optional[Union[Dict, Var[Dict]]] = None,
use_resize_handler: Optional[Union[Var[bool], bool]] = 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_after_plot: Optional[EventType[()]] = None,
on_animated: Optional[EventType[()]] = None,
on_animating_frame: Optional[EventType[()]] = None,
on_animation_interrupted: Optional[EventType[()]] = None,
on_autosize: Optional[EventType[()]] = None,
on_before_hover: Optional[EventType[()]] = None,
on_blur: Optional[EventType[()]] = None,
on_button_clicked: Optional[EventType[()]] = None,
on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_context_menu: Optional[EventType[()]] = None,
on_deselect: Optional[EventType[()]] = None,
on_double_click: Optional[EventType[()]] = None,
on_focus: Optional[EventType[()]] = None,
on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_mount: Optional[EventType[()]] = None,
on_mouse_down: Optional[EventType[()]] = None,
on_mouse_enter: Optional[EventType[()]] = None,
on_mouse_leave: Optional[EventType[()]] = None,
on_mouse_move: Optional[EventType[()]] = None,
on_mouse_out: Optional[EventType[()]] = None,
on_mouse_over: Optional[EventType[()]] = None,
on_mouse_up: Optional[EventType[()]] = None,
on_redraw: Optional[EventType[()]] = None,
on_relayout: Optional[EventType[()]] = None,
on_relayouting: Optional[EventType[()]] = None,
on_restyle: Optional[EventType[()]] = None,
on_scroll: Optional[EventType[()]] = None,
on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_transition_interrupted: Optional[EventType[()]] = None,
on_transitioning: Optional[EventType[()]] = None,
on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_unmount: Optional[EventType[()]] = None,
**props,
) -> "PlotlyMapbox":
"""Create the Plotly component.
Args:
*children: The children of the component.
data: The figure to display. This can be a plotly figure or a plotly data json.
layout: The layout of the graph.
template: The template for visual appearance of the graph.
config: The config of the graph.
use_resize_handler: If true, the graph will resize when the window is resized.
on_after_plot: Fired after the plot is redrawn.
on_animated: Fired after the plot was animated.
on_animating_frame: Fired while animating a single frame (does not currently pass data through).
on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example).
on_autosize: Fired when the plot is responsively sized.
on_before_hover: Fired whenever mouse moves over a plot.
on_button_clicked: Fired when a plotly UI button is clicked.
on_click: Fired when the plot is clicked.
on_deselect: Fired when a selection is cleared (via double click).
on_double_click: Fired when the plot is double clicked.
on_hover: Fired when a plot element is hovered over.
on_relayout: Fired after the plot is laid out (zoom, pan, etc).
on_relayouting: Fired while the plot is being laid out.
on_restyle: Fired after the plot style is changed.
on_redraw: Fired after the plot is redrawn.
on_selected: Fired after selecting plot elements.
on_selecting: Fired while dragging a selection.
on_transitioning: Fired while an animation is occurring.
on_transition_interrupted: Fired when a transition is stopped early.
on_unhover: Fired when a hovered element is no longer hovered.
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
class_name: The class name for the component.
autofocus: Whether the component should take the focus once the page is loaded
custom_attrs: custom attribute
**props: The properties of the component.
Returns:
The Plotly component.
"""
...
class PlotlyFinance(Plotly):
def add_imports(self) -> ImportDict | list[ImportDict]: ...
@overload
@classmethod
def create( # type: ignore
cls,
*children,
data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore
layout: Optional[Union[Dict, Var[Dict]]] = None,
template: Optional[Union[Template, Var[Template]]] = None, # type: ignore
config: Optional[Union[Dict, Var[Dict]]] = None,
use_resize_handler: Optional[Union[Var[bool], bool]] = 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_after_plot: Optional[EventType[()]] = None,
on_animated: Optional[EventType[()]] = None,
on_animating_frame: Optional[EventType[()]] = None,
on_animation_interrupted: Optional[EventType[()]] = None,
on_autosize: Optional[EventType[()]] = None,
on_before_hover: Optional[EventType[()]] = None,
on_blur: Optional[EventType[()]] = None,
on_button_clicked: Optional[EventType[()]] = None,
on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_context_menu: Optional[EventType[()]] = None,
on_deselect: Optional[EventType[()]] = None,
on_double_click: Optional[EventType[()]] = None,
on_focus: Optional[EventType[()]] = None,
on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_mount: Optional[EventType[()]] = None,
on_mouse_down: Optional[EventType[()]] = None,
on_mouse_enter: Optional[EventType[()]] = None,
on_mouse_leave: Optional[EventType[()]] = None,
on_mouse_move: Optional[EventType[()]] = None,
on_mouse_out: Optional[EventType[()]] = None,
on_mouse_over: Optional[EventType[()]] = None,
on_mouse_up: Optional[EventType[()]] = None,
on_redraw: Optional[EventType[()]] = None,
on_relayout: Optional[EventType[()]] = None,
on_relayouting: Optional[EventType[()]] = None,
on_restyle: Optional[EventType[()]] = None,
on_scroll: Optional[EventType[()]] = None,
on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_transition_interrupted: Optional[EventType[()]] = None,
on_transitioning: Optional[EventType[()]] = None,
on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_unmount: Optional[EventType[()]] = None,
**props,
) -> "PlotlyFinance":
"""Create the Plotly component.
Args:
*children: The children of the component.
data: The figure to display. This can be a plotly figure or a plotly data json.
layout: The layout of the graph.
template: The template for visual appearance of the graph.
config: The config of the graph.
use_resize_handler: If true, the graph will resize when the window is resized.
on_after_plot: Fired after the plot is redrawn.
on_animated: Fired after the plot was animated.
on_animating_frame: Fired while animating a single frame (does not currently pass data through).
on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example).
on_autosize: Fired when the plot is responsively sized.
on_before_hover: Fired whenever mouse moves over a plot.
on_button_clicked: Fired when a plotly UI button is clicked.
on_click: Fired when the plot is clicked.
on_deselect: Fired when a selection is cleared (via double click).
on_double_click: Fired when the plot is double clicked.
on_hover: Fired when a plot element is hovered over.
on_relayout: Fired after the plot is laid out (zoom, pan, etc).
on_relayouting: Fired while the plot is being laid out.
on_restyle: Fired after the plot style is changed.
on_redraw: Fired after the plot is redrawn.
on_selected: Fired after selecting plot elements.
on_selecting: Fired while dragging a selection.
on_transitioning: Fired while an animation is occurring.
on_transition_interrupted: Fired when a transition is stopped early.
on_unhover: Fired when a hovered element is no longer hovered.
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
class_name: The class name for the component.
autofocus: Whether the component should take the focus once the page is loaded
custom_attrs: custom attribute
**props: The properties of the component.
Returns:
The Plotly component.
"""
...
class PlotlyStrict(Plotly):
def add_imports(self) -> ImportDict | list[ImportDict]: ...
@overload
@classmethod
def create( # type: ignore
cls,
*children,
data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore
layout: Optional[Union[Dict, Var[Dict]]] = None,
template: Optional[Union[Template, Var[Template]]] = None, # type: ignore
config: Optional[Union[Dict, Var[Dict]]] = None,
use_resize_handler: Optional[Union[Var[bool], bool]] = 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_after_plot: Optional[EventType[()]] = None,
on_animated: Optional[EventType[()]] = None,
on_animating_frame: Optional[EventType[()]] = None,
on_animation_interrupted: Optional[EventType[()]] = None,
on_autosize: Optional[EventType[()]] = None,
on_before_hover: Optional[EventType[()]] = None,
on_blur: Optional[EventType[()]] = None,
on_button_clicked: Optional[EventType[()]] = None,
on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_context_menu: Optional[EventType[()]] = None,
on_deselect: Optional[EventType[()]] = None,
on_double_click: Optional[EventType[()]] = None,
on_focus: Optional[EventType[()]] = None,
on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_mount: Optional[EventType[()]] = None,
on_mouse_down: Optional[EventType[()]] = None,
on_mouse_enter: Optional[EventType[()]] = None,
on_mouse_leave: Optional[EventType[()]] = None,
on_mouse_move: Optional[EventType[()]] = None,
on_mouse_out: Optional[EventType[()]] = None,
on_mouse_over: Optional[EventType[()]] = None,
on_mouse_up: Optional[EventType[()]] = None,
on_redraw: Optional[EventType[()]] = None,
on_relayout: Optional[EventType[()]] = None,
on_relayouting: Optional[EventType[()]] = None,
on_restyle: Optional[EventType[()]] = None,
on_scroll: Optional[EventType[()]] = None,
on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_transition_interrupted: Optional[EventType[()]] = None,
on_transitioning: Optional[EventType[()]] = None,
on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None,
on_unmount: Optional[EventType[()]] = None,
**props,
) -> "PlotlyStrict":
"""Create the Plotly component.
Args:
*children: The children of the component.
data: The figure to display. This can be a plotly figure or a plotly data json.
layout: The layout of the graph.
template: The template for visual appearance of the graph.
config: The config of the graph.
use_resize_handler: If true, the graph will resize when the window is resized.
on_after_plot: Fired after the plot is redrawn.
on_animated: Fired after the plot was animated.
on_animating_frame: Fired while animating a single frame (does not currently pass data through).
on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example).
on_autosize: Fired when the plot is responsively sized.
on_before_hover: Fired whenever mouse moves over a plot.
on_button_clicked: Fired when a plotly UI button is clicked.
on_click: Fired when the plot is clicked.
on_deselect: Fired when a selection is cleared (via double click).
on_double_click: Fired when the plot is double clicked.
on_hover: Fired when a plot element is hovered over.
on_relayout: Fired after the plot is laid out (zoom, pan, etc).
on_relayouting: Fired while the plot is being laid out.
on_restyle: Fired after the plot style is changed.
on_redraw: Fired after the plot is redrawn.
on_selected: Fired after selecting plot elements.
on_selecting: Fired while dragging a selection.
on_transitioning: Fired while an animation is occurring.
on_transition_interrupted: Fired when a transition is stopped early.
on_unhover: Fired when a hovered element is no longer hovered.
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
class_name: The class name for the component.
autofocus: Whether the component should take the focus once the page is loaded
custom_attrs: custom attribute
**props: The properties of the component.
Returns:
The Plotly component.
"""
...

View File

@ -144,7 +144,7 @@ class ColorModeIconButton(IconButton):
if allow_system: if allow_system:
def color_mode_item(_color_mode: str): def color_mode_item(_color_mode: Literal["light", "dark", "system"]):
return dropdown_menu.item( return dropdown_menu.item(
_color_mode.title(), on_click=set_color_mode(_color_mode) _color_mode.title(), on_click=set_color_mode(_color_mode)
) )

View File

@ -8,6 +8,7 @@ from reflex.base import Base
from reflex.components.component import Component, ComponentNamespace from reflex.components.component import Component, ComponentNamespace
from reflex.components.lucide.icon import Icon from reflex.components.lucide.icon import Icon
from reflex.components.props import NoExtrasAllowedProps, PropsBase from reflex.components.props import NoExtrasAllowedProps, PropsBase
from reflex.constants.base import Dirs
from reflex.event import EventSpec, run_script from reflex.event import EventSpec, run_script
from reflex.style import Style, resolved_color_mode from reflex.style import Style, resolved_color_mode
from reflex.utils import format from reflex.utils import format
@ -27,7 +28,10 @@ LiteralPosition = Literal[
"bottom-right", "bottom-right",
] ]
toast_ref = Var(_js_expr="refs['__toast']") toast_ref = Var(
_js_expr="refs['__toast']",
_var_data=VarData(imports={f"$/{Dirs.STATE_PATH}": [ImportVar(tag="refs")]}),
)
class ToastAction(Base): class ToastAction(Base):
@ -327,6 +331,19 @@ class Toaster(Component):
""" """
return Toaster.send_toast(message, level="success", **kwargs) return Toaster.send_toast(message, level="success", **kwargs)
@staticmethod
def toast_loading(message: str | Var = "", **kwargs: Any):
"""Display a loading toast message.
Args:
message: The message to display.
**kwargs: Additional toast props.
Returns:
The toast event.
"""
return Toaster.send_toast(message, level="loading", **kwargs)
@staticmethod @staticmethod
def toast_dismiss(id: Var | str | None = None): def toast_dismiss(id: Var | str | None = None):
"""Dismiss a toast. """Dismiss a toast.
@ -378,6 +395,7 @@ class ToastNamespace(ComponentNamespace):
warning = staticmethod(Toaster.toast_warning) warning = staticmethod(Toaster.toast_warning)
error = staticmethod(Toaster.toast_error) error = staticmethod(Toaster.toast_error)
success = staticmethod(Toaster.toast_success) success = staticmethod(Toaster.toast_success)
loading = staticmethod(Toaster.toast_loading)
dismiss = staticmethod(Toaster.toast_dismiss) dismiss = staticmethod(Toaster.toast_dismiss)
__call__ = staticmethod(Toaster.send_toast) __call__ = staticmethod(Toaster.send_toast)

View File

@ -9,9 +9,12 @@ from reflex.base import Base
from reflex.components.component import Component, ComponentNamespace from reflex.components.component import Component, ComponentNamespace
from reflex.components.lucide.icon import Icon from reflex.components.lucide.icon import Icon
from reflex.components.props import NoExtrasAllowedProps, PropsBase from reflex.components.props import NoExtrasAllowedProps, PropsBase
from reflex.constants.base import Dirs
from reflex.event import EventSpec, EventType from reflex.event import EventSpec, EventType
from reflex.style import Style from reflex.style import Style
from reflex.utils.imports import ImportVar
from reflex.utils.serializers import serializer from reflex.utils.serializers import serializer
from reflex.vars import VarData
from reflex.vars.base import Var from reflex.vars.base import Var
LiteralPosition = Literal[ LiteralPosition = Literal[
@ -22,7 +25,10 @@ LiteralPosition = Literal[
"bottom-center", "bottom-center",
"bottom-right", "bottom-right",
] ]
toast_ref = Var(_js_expr="refs['__toast']") toast_ref = Var(
_js_expr="refs['__toast']",
_var_data=VarData(imports={f"$/{Dirs.STATE_PATH}": [ImportVar(tag="refs")]}),
)
class ToastAction(Base): class ToastAction(Base):
label: str label: str
@ -70,6 +76,8 @@ class Toaster(Component):
@staticmethod @staticmethod
def toast_success(message: str | Var = "", **kwargs: Any): ... def toast_success(message: str | Var = "", **kwargs: Any): ...
@staticmethod @staticmethod
def toast_loading(message: str | Var = "", **kwargs: Any): ...
@staticmethod
def toast_dismiss(id: Var | str | None = None): ... def toast_dismiss(id: Var | str | None = None): ...
@overload @overload
@classmethod @classmethod
@ -172,6 +180,7 @@ class ToastNamespace(ComponentNamespace):
warning = staticmethod(Toaster.toast_warning) warning = staticmethod(Toaster.toast_warning)
error = staticmethod(Toaster.toast_error) error = staticmethod(Toaster.toast_error)
success = staticmethod(Toaster.toast_success) success = staticmethod(Toaster.toast_success)
loading = staticmethod(Toaster.toast_loading)
dismiss = staticmethod(Toaster.toast_dismiss) dismiss = staticmethod(Toaster.toast_dismiss)
@staticmethod @staticmethod

View File

@ -134,6 +134,10 @@ class IterTag(Tag):
if isinstance(component, (Foreach, Cond)): if isinstance(component, (Foreach, Cond)):
component = Fragment.create(component) component = Fragment.create(component)
# If the component is a tuple, unpack and wrap it in a fragment.
if isinstance(component, tuple):
component = Fragment.create(*component)
# Set the component key. # Set the component key.
if component.key is None: if component.key is None:
component.key = index component.key = index

View File

@ -3,7 +3,7 @@
from __future__ import annotations from __future__ import annotations
import dataclasses import dataclasses
from typing import Any, Dict, List, Optional, Sequence, Union from typing import Any, Dict, List, Mapping, Optional, Sequence
from reflex.event import EventChain from reflex.event import EventChain
from reflex.utils import format, types from reflex.utils import format, types
@ -103,7 +103,7 @@ class Tag:
{ {
format.to_camel_case(name, allow_hyphens=True): ( format.to_camel_case(name, allow_hyphens=True): (
prop prop
if types._isinstance(prop, Union[EventChain, dict]) if types._isinstance(prop, (EventChain, Mapping))
else LiteralVar.create(prop) else LiteralVar.create(prop)
) # rx.color is always a string ) # rx.color is always a string
for name, prop in kwargs.items() for name, prop in kwargs.items()

View File

@ -2,20 +2,25 @@
from __future__ import annotations from __future__ import annotations
import concurrent.futures
import dataclasses import dataclasses
import enum import enum
import importlib import importlib
import inspect import inspect
import multiprocessing
import os import os
import platform
import sys import sys
import threading import threading
import urllib.parse import urllib.parse
from functools import lru_cache
from importlib.util import find_spec from importlib.util import find_spec
from pathlib import Path from pathlib import Path
from types import ModuleType from types import ModuleType
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
Callable,
Dict, Dict,
Generic, Generic,
List, List,
@ -23,10 +28,15 @@ from typing import (
Set, Set,
TypeVar, TypeVar,
get_args, get_args,
get_origin,
) )
from reflex_cli.constants.hosting import Hosting
from typing_extensions import Annotated, get_type_hints from typing_extensions import Annotated, get_type_hints
from reflex import constants
from reflex.base import Base
from reflex.utils import console
from reflex.utils.exceptions import ConfigError, EnvironmentVarValueError from reflex.utils.exceptions import ConfigError, EnvironmentVarValueError
from reflex.utils.types import GenericType, is_union, value_inside_optional from reflex.utils.types import GenericType, is_union, value_inside_optional
@ -35,11 +45,11 @@ try:
except ModuleNotFoundError: except ModuleNotFoundError:
import pydantic import pydantic
from reflex_cli.constants.hosting import Hosting
from reflex import constants try:
from reflex.base import Base from dotenv import load_dotenv # pyright: ignore [reportMissingImports]
from reflex.utils import console except ImportError:
load_dotenv = None
class DBConfig(Base): class DBConfig(Base):
@ -304,6 +314,15 @@ def interpret_env_var_value(
return interpret_path_env(value, field_name) return interpret_path_env(value, field_name)
elif field_type is ExistingPath: elif field_type is ExistingPath:
return interpret_existing_path_env(value, field_name) return interpret_existing_path_env(value, field_name)
elif get_origin(field_type) is list:
return [
interpret_env_var_value(
v,
get_args(field_type)[0],
f"{field_name}[{i}]",
)
for i, v in enumerate(value.split(":"))
]
elif inspect.isclass(field_type) and issubclass(field_type, enum.Enum): elif inspect.isclass(field_type) and issubclass(field_type, enum.Enum):
return interpret_enum_env(value, field_type, field_name) return interpret_enum_env(value, field_type, field_name)
@ -387,7 +406,24 @@ class EnvVar(Generic[T]):
else: else:
if isinstance(value, enum.Enum): if isinstance(value, enum.Enum):
value = value.value value = value.value
os.environ[self.name] = str(value) if isinstance(value, list):
str_value = ":".join(str(v) for v in value)
else:
str_value = str(value)
os.environ[self.name] = str_value
@lru_cache()
def get_type_hints_environment(cls: type) -> dict[str, Any]:
"""Get the type hints for the environment variables.
Args:
cls: The class.
Returns:
The type hints.
"""
return get_type_hints(cls)
class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration] class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
@ -416,7 +452,9 @@ class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
""" """
self.name = name self.name = name
def __get__(self, instance: Any, owner: Any): def __get__(
self, instance: EnvironmentVariables, owner: type[EnvironmentVariables]
):
"""Get the EnvVar instance. """Get the EnvVar instance.
Args: Args:
@ -426,7 +464,7 @@ class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
Returns: Returns:
The EnvVar instance. The EnvVar instance.
""" """
type_ = get_args(get_type_hints(owner)[self.name])[0] type_ = get_args(get_type_hints_environment(owner)[self.name])[0]
env_name = self.name env_name = self.name
if self.internal: if self.internal:
env_name = f"__{env_name}" env_name = f"__{env_name}"
@ -463,6 +501,95 @@ class PerformanceMode(enum.Enum):
OFF = "off" OFF = "off"
class ExecutorType(enum.Enum):
"""Executor for compiling the frontend."""
THREAD = "thread"
PROCESS = "process"
MAIN_THREAD = "main_thread"
@classmethod
def get_executor_from_environment(cls):
"""Get the executor based on the environment variables.
Returns:
The executor.
"""
executor_type = environment.REFLEX_COMPILE_EXECUTOR.get()
reflex_compile_processes = environment.REFLEX_COMPILE_PROCESSES.get()
reflex_compile_threads = environment.REFLEX_COMPILE_THREADS.get()
# By default, use the main thread. Unless the user has specified a different executor.
# Using a process pool is much faster, but not supported on all platforms. It's gated behind a flag.
if executor_type is None:
if (
platform.system() not in ("Linux", "Darwin")
and reflex_compile_processes is not None
):
console.warn("Multiprocessing is only supported on Linux and MacOS.")
if (
platform.system() in ("Linux", "Darwin")
and reflex_compile_processes is not None
):
if reflex_compile_processes == 0:
console.warn(
"Number of processes must be greater than 0. If you want to use the default number of processes, set REFLEX_COMPILE_EXECUTOR to 'process'. Defaulting to None."
)
reflex_compile_processes = None
elif reflex_compile_processes < 0:
console.warn(
"Number of processes must be greater than 0. Defaulting to None."
)
reflex_compile_processes = None
executor_type = ExecutorType.PROCESS
elif reflex_compile_threads is not None:
if reflex_compile_threads == 0:
console.warn(
"Number of threads must be greater than 0. If you want to use the default number of threads, set REFLEX_COMPILE_EXECUTOR to 'thread'. Defaulting to None."
)
reflex_compile_threads = None
elif reflex_compile_threads < 0:
console.warn(
"Number of threads must be greater than 0. Defaulting to None."
)
reflex_compile_threads = None
executor_type = ExecutorType.THREAD
else:
executor_type = ExecutorType.MAIN_THREAD
match executor_type:
case ExecutorType.PROCESS:
executor = concurrent.futures.ProcessPoolExecutor(
max_workers=reflex_compile_processes,
mp_context=multiprocessing.get_context("fork"),
)
case ExecutorType.THREAD:
executor = concurrent.futures.ThreadPoolExecutor(
max_workers=reflex_compile_threads
)
case ExecutorType.MAIN_THREAD:
FUTURE_RESULT_TYPE = TypeVar("FUTURE_RESULT_TYPE")
class MainThreadExecutor:
def __enter__(self):
return self
def __exit__(self, *args):
pass
def submit(
self, fn: Callable[..., FUTURE_RESULT_TYPE], *args, **kwargs
) -> concurrent.futures.Future[FUTURE_RESULT_TYPE]:
future_job = concurrent.futures.Future()
future_job.set_result(fn(*args, **kwargs))
return future_job
executor = MainThreadExecutor()
return executor
class EnvironmentVariables: class EnvironmentVariables:
"""Environment variables class to instantiate environment variables.""" """Environment variables class to instantiate environment variables."""
@ -504,6 +631,8 @@ class EnvironmentVariables:
Path(constants.Dirs.UPLOADED_FILES) Path(constants.Dirs.UPLOADED_FILES)
) )
REFLEX_COMPILE_EXECUTOR: EnvVar[Optional[ExecutorType]] = env_var(None)
# Whether to use separate processes to compile the frontend and how many. If not set, defaults to thread executor. # Whether to use separate processes to compile the frontend and how many. If not set, defaults to thread executor.
REFLEX_COMPILE_PROCESSES: EnvVar[Optional[int]] = env_var(None) REFLEX_COMPILE_PROCESSES: EnvVar[Optional[int]] = env_var(None)
@ -544,6 +673,12 @@ class EnvironmentVariables:
# Whether to run the frontend only. Exclusive with REFLEX_BACKEND_ONLY. # Whether to run the frontend only. Exclusive with REFLEX_BACKEND_ONLY.
REFLEX_FRONTEND_ONLY: EnvVar[bool] = env_var(False) REFLEX_FRONTEND_ONLY: EnvVar[bool] = env_var(False)
# The port to run the frontend on.
REFLEX_FRONTEND_PORT: EnvVar[int | None] = env_var(None)
# The port to run the backend on.
REFLEX_BACKEND_PORT: EnvVar[int | None] = env_var(None)
# Reflex internal env to reload the config. # Reflex internal env to reload the config.
RELOAD_CONFIG: EnvVar[bool] = env_var(False, internal=True) RELOAD_CONFIG: EnvVar[bool] = env_var(False, internal=True)
@ -563,7 +698,7 @@ class EnvironmentVariables:
REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True) REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True)
# In which performance mode to run the app. # In which performance mode to run the app.
REFLEX_PERF_MODE: EnvVar[Optional[PerformanceMode]] = env_var(PerformanceMode.WARN) REFLEX_PERF_MODE: EnvVar[PerformanceMode] = env_var(PerformanceMode.WARN)
# The maximum size of the reflex state in kilobytes. # The maximum size of the reflex state in kilobytes.
REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000) REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000)
@ -571,6 +706,12 @@ class EnvironmentVariables:
# Whether to use the turbopack bundler. # Whether to use the turbopack bundler.
REFLEX_USE_TURBOPACK: EnvVar[bool] = env_var(True) REFLEX_USE_TURBOPACK: EnvVar[bool] = env_var(True)
# Additional paths to include in the hot reload. Separated by a colon.
REFLEX_HOT_RELOAD_INCLUDE_PATHS: EnvVar[List[Path]] = env_var([])
# Paths to exclude from the hot reload. Takes precedence over include paths. Separated by a colon.
REFLEX_HOT_RELOAD_EXCLUDE_PATHS: EnvVar[List[Path]] = env_var([])
environment = EnvironmentVariables() environment = EnvironmentVariables()
@ -604,6 +745,7 @@ class Config(Base):
"""Pydantic config for the config.""" """Pydantic config for the config."""
validate_assignment = True validate_assignment = True
use_enum_values = False
# The name of the app (should match the name of the app directory). # The name of the app (should match the name of the app directory).
app_name: str app_name: str
@ -615,19 +757,21 @@ class Config(Base):
loglevel: constants.LogLevel = constants.LogLevel.DEFAULT loglevel: constants.LogLevel = constants.LogLevel.DEFAULT
# The port to run the frontend on. NOTE: When running in dev mode, the next available port will be used if this is taken. # The port to run the frontend on. NOTE: When running in dev mode, the next available port will be used if this is taken.
frontend_port: int = constants.DefaultPorts.FRONTEND_PORT frontend_port: int | None = None
# The path to run the frontend on. For example, "/app" will run the frontend on http://localhost:3000/app # The path to run the frontend on. For example, "/app" will run the frontend on http://localhost:3000/app
frontend_path: str = "" frontend_path: str = ""
# The port to run the backend on. NOTE: When running in dev mode, the next available port will be used if this is taken. # The port to run the backend on. NOTE: When running in dev mode, the next available port will be used if this is taken.
backend_port: int = constants.DefaultPorts.BACKEND_PORT backend_port: int | None = None
# The backend url the frontend will connect to. This must be updated if the backend is hosted elsewhere, or in production. # The backend url the frontend will connect to. This must be updated if the backend is hosted elsewhere, or in production.
api_url: str = f"http://localhost:{backend_port}" api_url: str = f"http://localhost:{constants.DefaultPorts.BACKEND_PORT}"
# The url the frontend will be hosted on. # The url the frontend will be hosted on.
deploy_url: Optional[str] = f"http://localhost:{frontend_port}" deploy_url: Optional[str] = (
f"http://localhost:{constants.DefaultPorts.FRONTEND_PORT}"
)
# The url the backend will be hosted on. # The url the backend will be hosted on.
backend_host: str = "0.0.0.0" backend_host: str = "0.0.0.0"
@ -679,7 +823,7 @@ class Config(Base):
# Number of gunicorn workers from user # Number of gunicorn workers from user
gunicorn_workers: Optional[int] = None gunicorn_workers: Optional[int] = None
# Number of requests before a worker is restarted # Number of requests before a worker is restarted; set to 0 to disable
gunicorn_max_requests: int = 100 gunicorn_max_requests: int = 100
# Variance limit for max requests; gunicorn only # Variance limit for max requests; gunicorn only
@ -734,6 +878,9 @@ class Config(Base):
self._non_default_attributes.update(kwargs) self._non_default_attributes.update(kwargs)
self._replace_defaults(**kwargs) self._replace_defaults(**kwargs)
# Set the log level for this process
console.set_log_level(self.loglevel)
if ( if (
self.state_manager_mode == constants.StateManagerMode.REDIS self.state_manager_mode == constants.StateManagerMode.REDIS
and not self.redis_url and not self.redis_url
@ -773,16 +920,15 @@ class Config(Base):
Returns: Returns:
The updated config values. The updated config values.
""" """
if self.env_file: env_file = self.env_file or os.environ.get("ENV_FILE", None)
try: if env_file:
from dotenv import load_dotenv # pyright: ignore [reportMissingImports] if load_dotenv is None:
# load env file if exists
load_dotenv(self.env_file, override=True)
except ImportError:
console.error( console.error(
"""The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.0.1"`.""" """The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.0.1"`."""
) )
else:
# load env file if exists
load_dotenv(env_file, override=True)
updated_values = {} updated_values = {}
# Iterate over the fields. # Iterate over the fields.

View File

@ -26,6 +26,7 @@ from typing import (
from typing_extensions import ( from typing_extensions import (
Protocol, Protocol,
Self,
TypeAliasType, TypeAliasType,
TypedDict, TypedDict,
TypeVar, TypeVar,
@ -110,7 +111,7 @@ class EventActionsMixin:
event_actions: Dict[str, Union[bool, int]] = dataclasses.field(default_factory=dict) event_actions: Dict[str, Union[bool, int]] = dataclasses.field(default_factory=dict)
@property @property
def stop_propagation(self): def stop_propagation(self) -> Self:
"""Stop the event from bubbling up the DOM tree. """Stop the event from bubbling up the DOM tree.
Returns: Returns:
@ -122,7 +123,7 @@ class EventActionsMixin:
) )
@property @property
def prevent_default(self): def prevent_default(self) -> Self:
"""Prevent the default behavior of the event. """Prevent the default behavior of the event.
Returns: Returns:
@ -133,7 +134,7 @@ class EventActionsMixin:
event_actions={"preventDefault": True, **self.event_actions}, event_actions={"preventDefault": True, **self.event_actions},
) )
def throttle(self, limit_ms: int): def throttle(self, limit_ms: int) -> Self:
"""Throttle the event handler. """Throttle the event handler.
Args: Args:
@ -147,7 +148,7 @@ class EventActionsMixin:
event_actions={"throttle": limit_ms, **self.event_actions}, event_actions={"throttle": limit_ms, **self.event_actions},
) )
def debounce(self, delay_ms: int): def debounce(self, delay_ms: int) -> Self:
"""Debounce the event handler. """Debounce the event handler.
Args: Args:
@ -162,7 +163,7 @@ class EventActionsMixin:
) )
@property @property
def temporal(self): def temporal(self) -> Self:
"""Do not queue the event if the backend is down. """Do not queue the event if the backend is down.
Returns: Returns:
@ -1773,7 +1774,7 @@ V4 = TypeVar("V4")
V5 = TypeVar("V5") V5 = TypeVar("V5")
class EventCallback(Generic[Unpack[P]]): class EventCallback(Generic[Unpack[P]], EventActionsMixin):
"""A descriptor that wraps a function to be used as an event.""" """A descriptor that wraps a function to be used as an event."""
def __init__(self, func: Callable[[Any, Unpack[P]], Any]): def __init__(self, func: Callable[[Any, Unpack[P]], Any]):
@ -1784,24 +1785,6 @@ class EventCallback(Generic[Unpack[P]]):
""" """
self.func = func self.func = func
@property
def prevent_default(self):
"""Prevent default behavior.
Returns:
The event callback with prevent default behavior.
"""
return self
@property
def stop_propagation(self):
"""Stop event propagation.
Returns:
The event callback with stop propagation behavior.
"""
return self
@overload @overload
def __call__( def __call__(
self: EventCallback[Unpack[Q]], self: EventCallback[Unpack[Q]],

View File

@ -127,8 +127,8 @@ def _run(
env: constants.Env = constants.Env.DEV, env: constants.Env = constants.Env.DEV,
frontend: bool = True, frontend: bool = True,
backend: bool = True, backend: bool = True,
frontend_port: int = config.frontend_port, frontend_port: int | None = None,
backend_port: int = config.backend_port, backend_port: int | None = None,
backend_host: str = config.backend_host, backend_host: str = config.backend_host,
loglevel: constants.LogLevel = config.loglevel, loglevel: constants.LogLevel = config.loglevel,
): ):
@ -145,10 +145,7 @@ def _run(
exec.output_system_info() exec.output_system_info()
# If no --frontend-only and no --backend-only, then turn on frontend and backend both # If no --frontend-only and no --backend-only, then turn on frontend and backend both
if not frontend and not backend: frontend, backend = prerequisites.check_running_mode(frontend, backend)
frontend = True
backend = True
if not frontend and backend: if not frontend and backend:
_skip_compile() _skip_compile()
@ -161,17 +158,28 @@ def _run(
# Find the next available open port if applicable. # Find the next available open port if applicable.
if frontend: if frontend:
auto_increment_frontend = not bool(frontend_port or config.frontend_port)
frontend_port = processes.handle_port( frontend_port = processes.handle_port(
"frontend", "frontend",
frontend_port, (
constants.DefaultPorts.FRONTEND_PORT, frontend_port
or config.frontend_port
or constants.DefaultPorts.FRONTEND_PORT
),
auto_increment=auto_increment_frontend,
) )
if backend: if backend:
auto_increment_backend = not bool(backend_port or config.backend_port)
backend_port = processes.handle_port( backend_port = processes.handle_port(
"backend", "backend",
backend_port, (
constants.DefaultPorts.BACKEND_PORT, backend_port
or config.backend_port
or constants.DefaultPorts.BACKEND_PORT
),
auto_increment=auto_increment_backend,
) )
# Apply the new ports to the config. # Apply the new ports to the config.
@ -249,7 +257,7 @@ def _run(
# Start the frontend and backend. # Start the frontend and backend.
with processes.run_concurrently_context(*commands): with processes.run_concurrently_context(*commands):
# In dev mode, run the backend on the main thread. # In dev mode, run the backend on the main thread.
if backend and env == constants.Env.DEV: if backend and backend_port and env == constants.Env.DEV:
backend_cmd( backend_cmd(
backend_host, int(backend_port), loglevel.subprocess_level(), frontend backend_host, int(backend_port), loglevel.subprocess_level(), frontend
) )
@ -278,10 +286,14 @@ def run(
envvar=environment.REFLEX_BACKEND_ONLY.name, envvar=environment.REFLEX_BACKEND_ONLY.name,
), ),
frontend_port: int = typer.Option( frontend_port: int = typer.Option(
config.frontend_port, help="Specify a different frontend port." config.frontend_port,
help="Specify a different frontend port.",
envvar=environment.REFLEX_FRONTEND_PORT.name,
), ),
backend_port: int = typer.Option( backend_port: int = typer.Option(
config.backend_port, help="Specify a different backend port." config.backend_port,
help="Specify a different backend port.",
envvar=environment.REFLEX_BACKEND_PORT.name,
), ),
backend_host: str = typer.Option( backend_host: str = typer.Option(
config.backend_host, help="Specify the backend host." config.backend_host, help="Specify the backend host."
@ -306,10 +318,18 @@ def export(
True, "--no-zip", help="Disable zip for backend and frontend exports." True, "--no-zip", help="Disable zip for backend and frontend exports."
), ),
frontend: bool = typer.Option( frontend: bool = typer.Option(
True, "--backend-only", help="Export only backend.", show_default=False False,
"--frontend-only",
help="Export only frontend.",
show_default=False,
envvar=environment.REFLEX_FRONTEND_ONLY.name,
), ),
backend: bool = typer.Option( backend: bool = typer.Option(
True, "--frontend-only", help="Export only frontend.", show_default=False False,
"--backend-only",
help="Export only backend.",
show_default=False,
envvar=environment.REFLEX_BACKEND_ONLY.name,
), ),
zip_dest_dir: str = typer.Option( zip_dest_dir: str = typer.Option(
str(Path.cwd()), str(Path.cwd()),
@ -332,7 +352,9 @@ def export(
from reflex.utils import export as export_utils from reflex.utils import export as export_utils
from reflex.utils import prerequisites from reflex.utils import prerequisites
if prerequisites.needs_reinit(frontend=True): frontend, backend = prerequisites.check_running_mode(frontend, backend)
if prerequisites.needs_reinit(frontend=frontend or not backend):
_init(name=config.app_name, loglevel=loglevel) _init(name=config.app_name, loglevel=loglevel)
if frontend and not config.show_built_with_reflex: if frontend and not config.show_built_with_reflex:

View File

@ -1747,6 +1747,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
Yields: Yields:
StateUpdate object StateUpdate object
Raises:
ValueError: If a string value is received for an int or float type and cannot be converted.
""" """
from reflex.utils import telemetry from reflex.utils import telemetry
@ -1784,12 +1787,25 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
hinted_args, (Base, BaseModelV1, BaseModelV2) hinted_args, (Base, BaseModelV1, BaseModelV2)
): ):
payload[arg] = hinted_args(**value) payload[arg] = hinted_args(**value)
if isinstance(value, list) and (hinted_args is set or hinted_args is Set): elif isinstance(value, list) and (hinted_args is set or hinted_args is Set):
payload[arg] = set(value) payload[arg] = set(value)
if isinstance(value, list) and ( elif isinstance(value, list) and (
hinted_args is tuple or hinted_args is Tuple hinted_args is tuple or hinted_args is Tuple
): ):
payload[arg] = tuple(value) payload[arg] = tuple(value)
elif isinstance(value, str) and (
hinted_args is int or hinted_args is float
):
try:
payload[arg] = hinted_args(value)
except ValueError:
raise ValueError(
f"Received a string value ({value}) for {arg} but expected a {hinted_args}"
) from None
else:
console.warn(
f"Received a string value ({value}) for {arg} but expected a {hinted_args}. A simple conversion was successful."
)
# Wrap the function in a try/except block. # Wrap the function in a try/except block.
try: try:
@ -2464,6 +2480,8 @@ class ComponentState(State, mixin=True):
Returns: Returns:
A new instance of the Component with an independent copy of the State. A new instance of the Component with an independent copy of the State.
""" """
from reflex.compiler.compiler import into_component
cls._per_component_state_instance_count += 1 cls._per_component_state_instance_count += 1
state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}" state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}"
component_state = type( component_state = type(
@ -2475,6 +2493,7 @@ class ComponentState(State, mixin=True):
# Save a reference to the dynamic state for pickle/unpickle. # Save a reference to the dynamic state for pickle/unpickle.
setattr(reflex.istate.dynamic, state_cls_name, component_state) setattr(reflex.istate.dynamic, state_cls_name, component_state)
component = component_state.get_component(*children, **props) component = component_state.get_component(*children, **props)
component = into_component(component)
component.State = component_state component.State = component_state
return component return component

View File

@ -12,7 +12,7 @@ from reflex.utils.exceptions import ReflexError
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
from reflex.utils.types import get_origin from reflex.utils.types import get_origin
from reflex.vars import VarData from reflex.vars import VarData
from reflex.vars.base import CallableVar, LiteralVar, Var from reflex.vars.base import LiteralVar, Var
from reflex.vars.function import FunctionVar from reflex.vars.function import FunctionVar
from reflex.vars.object import ObjectVar from reflex.vars.object import ObjectVar
@ -48,7 +48,6 @@ def _color_mode_var(_js_expr: str, _var_type: Type = str) -> Var:
).guess_type() ).guess_type()
@CallableVar
def set_color_mode( def set_color_mode(
new_color_mode: LiteralColorMode | Var[LiteralColorMode] | None = None, new_color_mode: LiteralColorMode | Var[LiteralColorMode] | None = None,
) -> Var[EventChain]: ) -> Var[EventChain]:

View File

@ -43,6 +43,7 @@ import reflex.utils.exec
import reflex.utils.format import reflex.utils.format
import reflex.utils.prerequisites import reflex.utils.prerequisites
import reflex.utils.processes import reflex.utils.processes
from reflex.components.component import CustomComponent
from reflex.config import environment from reflex.config import environment
from reflex.state import ( from reflex.state import (
BaseState, BaseState,
@ -254,6 +255,7 @@ class AppHarness:
# disable telemetry reporting for tests # disable telemetry reporting for tests
os.environ["TELEMETRY_ENABLED"] = "false" os.environ["TELEMETRY_ENABLED"] = "false"
CustomComponent.create().get_component.cache_clear()
self.app_path.mkdir(parents=True, exist_ok=True) self.app_path.mkdir(parents=True, exist_ok=True)
if self.app_source is not None: if self.app_source is not None:
app_globals = self._get_globals_from_signature(self.app_source) app_globals = self._get_globals_from_signature(self.app_source)

View File

@ -2,8 +2,11 @@
from __future__ import annotations from __future__ import annotations
import contextlib
import inspect import inspect
import os
import shutil import shutil
import time
from pathlib import Path from pathlib import Path
from types import FrameType from types import FrameType
@ -58,6 +61,9 @@ def set_log_level(log_level: LogLevel):
f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead." f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead."
) )
global _LOG_LEVEL global _LOG_LEVEL
if log_level != _LOG_LEVEL:
# Set the loglevel persistenly for subprocesses.
os.environ["LOGLEVEL"] = log_level.value
_LOG_LEVEL = log_level _LOG_LEVEL = log_level
@ -317,3 +323,20 @@ def status(*args, **kwargs):
A new status. A new status.
""" """
return _console.status(*args, **kwargs) return _console.status(*args, **kwargs)
@contextlib.contextmanager
def timing(msg: str):
"""Create a context manager to time a block of code.
Args:
msg: The message to display.
Yields:
None.
"""
start = time.time()
try:
yield
finally:
debug(f"[white]\\[timing] {msg}: {time.time() - start:.2f}s[/white]")

25
reflex/utils/decorator.py Normal file
View File

@ -0,0 +1,25 @@
"""Decorator utilities."""
from typing import Callable, TypeVar
T = TypeVar("T")
def once(f: Callable[[], T]) -> Callable[[], T]:
"""A decorator that calls the function once and caches the result.
Args:
f: The function to call.
Returns:
A function that calls the function once and caches the result.
"""
unset = object()
value: object | T = unset
def wrapper() -> T:
nonlocal value
value = f() if value is unset else value
return value # pyright: ignore[reportReturnType]
return wrapper

View File

@ -10,6 +10,7 @@ import re
import subprocess import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Sequence
from urllib.parse import urljoin from urllib.parse import urljoin
import psutil import psutil
@ -242,29 +243,63 @@ def run_backend(
run_uvicorn_backend(host, port, loglevel) run_uvicorn_backend(host, port, loglevel)
def get_reload_dirs() -> list[Path]: def get_reload_paths() -> Sequence[Path]:
"""Get the reload directories for the backend. """Get the reload paths for the backend.
Returns: Returns:
The reload directories for the backend. The reload paths for the backend.
""" """
config = get_config() config = get_config()
reload_dirs = [Path(config.app_name)] reload_paths = [Path(config.app_name).parent]
if config.app_module is not None and config.app_module.__file__: if config.app_module is not None and config.app_module.__file__:
module_path = Path(config.app_module.__file__).resolve().parent module_path = Path(config.app_module.__file__).resolve().parent
while module_path.parent.name: while module_path.parent.name and any(
if any( sibling_file.name == "__init__.py"
sibling_file.name == "__init__.py" for sibling_file in module_path.parent.iterdir()
for sibling_file in module_path.parent.iterdir() ):
): # go up a level to find dir without `__init__.py`
# go up a level to find dir without `__init__.py` module_path = module_path.parent
module_path = module_path.parent
else:
break
reload_dirs = [module_path] reload_paths = [module_path]
return reload_dirs
include_dirs = tuple(
map(Path.absolute, environment.REFLEX_HOT_RELOAD_INCLUDE_PATHS.get())
)
exclude_dirs = tuple(
map(Path.absolute, environment.REFLEX_HOT_RELOAD_EXCLUDE_PATHS.get())
)
def is_excluded_by_default(path: Path) -> bool:
if path.is_dir():
if path.name.startswith("."):
# exclude hidden directories
return True
if path.name.startswith("__"):
# ignore things like __pycache__
return True
return path.name in (".gitignore", "uploaded_files")
reload_paths = (
tuple(
path.absolute()
for dir in reload_paths
for path in dir.iterdir()
if not is_excluded_by_default(path)
)
+ include_dirs
)
if exclude_dirs:
reload_paths = tuple(
path
for path in reload_paths
if all(not path.samefile(exclude) for exclude in exclude_dirs)
)
console.debug(f"Reload paths: {list(map(str, reload_paths))}")
return reload_paths
def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel): def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
@ -283,7 +318,7 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
port=port, port=port,
log_level=loglevel.value, log_level=loglevel.value,
reload=True, reload=True,
reload_dirs=list(map(str, get_reload_dirs())), reload_dirs=list(map(str, get_reload_paths())),
) )
@ -310,8 +345,7 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
interface=Interfaces.ASGI, interface=Interfaces.ASGI,
log_level=LogLevels(loglevel.value), log_level=LogLevels(loglevel.value),
reload=True, reload=True,
reload_paths=get_reload_dirs(), reload_paths=get_reload_paths(),
reload_ignore_dirs=[".web", ".states"],
).serve() ).serve()
except ImportError: except ImportError:
console.error( console.error(
@ -368,34 +402,49 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
app_module = get_app_module() app_module = get_app_module()
run_backend_prod = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split()
run_backend_prod_windows = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split()
command = ( command = (
[ [
*run_backend_prod_windows, "uvicorn",
"--host", *(
host, [
"--port", "--limit-max-requests",
str(port), str(config.gunicorn_max_requests),
]
if config.gunicorn_max_requests > 0
else []
),
*("--timeout-keep-alive", str(config.timeout)),
*("--host", host),
*("--port", str(port)),
*("--workers", str(_get_backend_workers())),
app_module, app_module,
] ]
if constants.IS_WINDOWS if constants.IS_WINDOWS
else [ else [
*run_backend_prod, "gunicorn",
"--bind", *("--worker-class", config.gunicorn_worker_class),
f"{host}:{port}", *(
"--threads", [
str(_get_backend_workers()), "--max-requests",
str(config.gunicorn_max_requests),
"--max-requests-jitter",
str(config.gunicorn_max_requests_jitter),
]
if config.gunicorn_max_requests > 0
else []
),
"--preload",
*("--timeout", str(config.timeout)),
*("--bind", f"{host}:{port}"),
*("--threads", str(_get_backend_workers())),
f"{app_module}()", f"{app_module}()",
] ]
) )
command += [ command += [
"--log-level", *("--log-level", loglevel.value),
loglevel.value,
"--workers",
str(_get_backend_workers()),
] ]
processes.new_process( processes.new_process(
command, command,
run=True, run=True,

View File

@ -27,6 +27,36 @@ WRAP_MAP = {
} }
def length_of_largest_common_substring(str1: str, str2: str) -> int:
"""Find the length of the largest common substring between two strings.
Args:
str1: The first string.
str2: The second string.
Returns:
The length of the largest common substring.
"""
if not str1 or not str2:
return 0
# Create a matrix of size (len(str1) + 1) x (len(str2) + 1)
dp = [[0] * (len(str2) + 1) for _ in range(len(str1) + 1)]
# Variables to keep track of maximum length and ending position
max_length = 0
# Fill the dp matrix
for i in range(1, len(str1) + 1):
for j in range(1, len(str2) + 1):
if str1[i - 1] == str2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
if dp[i][j] > max_length:
max_length = dp[i][j]
return max_length
def get_close_char(open: str, close: str | None = None) -> str: def get_close_char(open: str, close: str | None = None) -> str:
"""Check if the given character is a valid brace. """Check if the given character is a valid brace.

View File

@ -109,6 +109,9 @@ class ImportVar:
# whether this import should be rendered or not # whether this import should be rendered or not
render: Optional[bool] = True render: Optional[bool] = True
# The path of the package to import from.
package_path: str = "/"
# whether this import package should be added to transpilePackages in next.config.js # whether this import package should be added to transpilePackages in next.config.js
# https://nextjs.org/docs/app/api-reference/next-config-js/transpilePackages # https://nextjs.org/docs/app/api-reference/next-config-js/transpilePackages
transpile: Optional[bool] = False transpile: Optional[bool] = False

View File

@ -260,3 +260,49 @@ def find_replace(directory: str | Path, find: str, replace: str):
text = filepath.read_text(encoding="utf-8") text = filepath.read_text(encoding="utf-8")
text = re.sub(find, replace, text) text = re.sub(find, replace, text)
filepath.write_text(text, encoding="utf-8") filepath.write_text(text, encoding="utf-8")
def samefile(file1: Path, file2: Path) -> bool:
"""Check if two files are the same.
Args:
file1: The first file.
file2: The second file.
Returns:
Whether the files are the same. If either file does not exist, returns False.
"""
if file1.exists() and file2.exists():
return file1.samefile(file2)
return False
def update_directory_tree(src: Path, dest: Path):
"""Recursively copies a directory tree from src to dest.
Only copies files if the destination file is missing or modified earlier than the source file.
Args:
src: Source directory
dest: Destination directory
Raises:
ValueError: If the source is not a directory
"""
if not src.is_dir():
raise ValueError(f"Source {src} is not a directory")
# Ensure the destination directory exists
dest.mkdir(parents=True, exist_ok=True)
for item in src.iterdir():
dest_item = dest / item.name
if item.is_dir():
# Recursively copy subdirectories
update_directory_tree(item, dest_item)
elif item.is_file() and (
not dest_item.exists() or item.stat().st_mtime > dest_item.stat().st_mtime
):
# Copy file if it doesn't exist in the destination or is older than the source
shutil.copy2(item, dest_item)

View File

@ -24,6 +24,7 @@ from datetime import datetime
from pathlib import Path from pathlib import Path
from types import ModuleType from types import ModuleType
from typing import Any, Callable, List, NamedTuple, Optional from typing import Any, Callable, List, NamedTuple, Optional
from urllib.parse import urlparse
import httpx import httpx
import typer import typer
@ -1234,6 +1235,21 @@ def install_frontend_packages(packages: set[str], config: Config):
) )
def check_running_mode(frontend: bool, backend: bool) -> tuple[bool, bool]:
"""Check if the app is running in frontend or backend mode.
Args:
frontend: Whether to run the frontend of the app.
backend: Whether to run the backend of the app.
Returns:
The running modes.
"""
if not frontend and not backend:
return True, True
return frontend, backend
def needs_reinit(frontend: bool = True) -> bool: def needs_reinit(frontend: bool = True) -> bool:
"""Check if an app needs to be reinitialized. """Check if an app needs to be reinitialized.
@ -1302,10 +1318,13 @@ def validate_bun():
""" """
bun_path = path_ops.get_bun_path() bun_path = path_ops.get_bun_path()
if bun_path and not bun_path.samefile(constants.Bun.DEFAULT_PATH): if bun_path is None:
return
if not path_ops.samefile(bun_path, constants.Bun.DEFAULT_PATH):
console.info(f"Using custom Bun path: {bun_path}") console.info(f"Using custom Bun path: {bun_path}")
bun_version = get_bun_version() bun_version = get_bun_version()
if not bun_version: if bun_version is None:
console.error( console.error(
"Failed to obtain bun version. Make sure the specified bun path in your config is correct." "Failed to obtain bun version. Make sure the specified bun path in your config is correct."
) )
@ -1670,9 +1689,11 @@ def validate_and_create_app_using_remote_template(
template_url = templates[template].code_url template_url = templates[template].code_url
else: else:
template_parsed_url = urlparse(template)
# Check if the template is a github repo. # Check if the template is a github repo.
if template.startswith("https://github.com"): if template_parsed_url.hostname == "github.com":
template_url = f"{template.strip('/').replace('.git', '')}/archive/main.zip" path = template_parsed_url.path.strip("/").removesuffix(".git")
template_url = f"https://github.com/{path}/archive/main.zip"
else: else:
console.error(f"Template `{template}` not found or invalid.") console.error(f"Template `{template}` not found or invalid.")
raise typer.Exit(1) raise typer.Exit(1)

View File

@ -116,17 +116,14 @@ def change_port(port: int, _type: str) -> int:
return new_port return new_port
def handle_port(service_name: str, port: int, default_port: int) -> int: def handle_port(service_name: str, port: int, auto_increment: bool) -> int:
"""Change port if the specified port is in use and is not explicitly specified as a CLI arg or config arg. """Change port if the specified port is in use and is not explicitly specified as a CLI arg or config arg.
otherwise tell the user the port is in use and exit the app. Otherwise tell the user the port is in use and exit the app.
We make an assumption that when port is the default port,then it hasn't been explicitly set since its not straightforward
to know whether a port was explicitly provided by the user unless its any other than the default.
Args: Args:
service_name: The frontend or backend. service_name: The frontend or backend.
port: The provided port. port: The provided port.
default_port: The default port number associated with the specified service. auto_increment: Whether to automatically increment the port.
Returns: Returns:
The port to run the service on. The port to run the service on.
@ -134,13 +131,15 @@ def handle_port(service_name: str, port: int, default_port: int) -> int:
Raises: Raises:
Exit:when the port is in use. Exit:when the port is in use.
""" """
if is_process_on_port(port): if (process := get_process_on_port(port)) is None:
if port == int(default_port): return port
return change_port(port, service_name) if auto_increment:
else: return change_port(port, service_name)
console.error(f"{service_name.capitalize()} port: {port} is already in use") else:
raise typer.Exit() console.error(
return port f"{service_name.capitalize()} port: {port} is already in use by PID: {process.pid}."
)
raise typer.Exit()
def new_process( def new_process(

View File

@ -95,6 +95,7 @@ GenericType = Union[Type, _GenericAlias]
# Valid state var types. # Valid state var types.
JSONType = {str, int, float, bool} JSONType = {str, int, float, bool}
PrimitiveType = Union[int, float, bool, str, list, dict, set, tuple] PrimitiveType = Union[int, float, bool, str, list, dict, set, tuple]
PrimitiveTypes = (int, float, bool, str, list, dict, set, tuple)
StateVar = Union[PrimitiveType, Base, None] StateVar = Union[PrimitiveType, Base, None]
StateIterVar = Union[list, set, tuple] StateIterVar = Union[list, set, tuple]
@ -551,13 +552,13 @@ def does_obj_satisfy_typed_dict(obj: Any, cls: GenericType) -> bool:
return required_keys.issubset(required_keys) return required_keys.issubset(required_keys)
def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool: def _isinstance(obj: Any, cls: GenericType, nested: int = 0) -> bool:
"""Check if an object is an instance of a class. """Check if an object is an instance of a class.
Args: Args:
obj: The object to check. obj: The object to check.
cls: The class to check against. cls: The class to check against.
nested: Whether the check is nested. nested: How many levels deep to check.
Returns: Returns:
Whether the object is an instance of the class. Whether the object is an instance of the class.
@ -565,15 +566,24 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
if cls is Any: if cls is Any:
return True return True
from reflex.vars import LiteralVar, Var
if cls is Var:
return isinstance(obj, Var)
if isinstance(obj, LiteralVar):
return _isinstance(obj._var_value, cls, nested=nested)
if isinstance(obj, Var):
return _issubclass(obj._var_type, cls)
if cls is None or cls is type(None): if cls is None or cls is type(None):
return obj is None return obj is None
if cls and is_union(cls):
return any(_isinstance(obj, arg, nested=nested) for arg in get_args(cls))
if is_literal(cls): if is_literal(cls):
return obj in get_args(cls) return obj in get_args(cls)
if is_union(cls):
return any(_isinstance(obj, arg) for arg in get_args(cls))
origin = get_origin(cls) origin = get_origin(cls)
if origin is None: if origin is None:
@ -596,38 +606,40 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
# cls is a simple generic class # cls is a simple generic class
return isinstance(obj, origin) return isinstance(obj, origin)
if nested and args: if nested > 0 and args:
if origin is list: if origin is list:
return isinstance(obj, list) and all( return isinstance(obj, list) and all(
_isinstance(item, args[0]) for item in obj _isinstance(item, args[0], nested=nested - 1) for item in obj
) )
if origin is tuple: if origin is tuple:
if args[-1] is Ellipsis: if args[-1] is Ellipsis:
return isinstance(obj, tuple) and all( return isinstance(obj, tuple) and all(
_isinstance(item, args[0]) for item in obj _isinstance(item, args[0], nested=nested - 1) for item in obj
) )
return ( return (
isinstance(obj, tuple) isinstance(obj, tuple)
and len(obj) == len(args) and len(obj) == len(args)
and all( and all(
_isinstance(item, arg) for item, arg in zip(obj, args, strict=True) _isinstance(item, arg, nested=nested - 1)
for item, arg in zip(obj, args, strict=True)
) )
) )
if origin in (dict, Breakpoints): if origin in (dict, Mapping, Breakpoints):
return isinstance(obj, dict) and all( return isinstance(obj, Mapping) and all(
_isinstance(key, args[0]) and _isinstance(value, args[1]) _isinstance(key, args[0], nested=nested - 1)
and _isinstance(value, args[1], nested=nested - 1)
for key, value in obj.items() for key, value in obj.items()
) )
if origin is set: if origin is set:
return isinstance(obj, set) and all( return isinstance(obj, set) and all(
_isinstance(item, args[0]) for item in obj _isinstance(item, args[0], nested=nested - 1) for item in obj
) )
if args: if args:
from reflex.vars import Field from reflex.vars import Field
if origin is Field: if origin is Field:
return _isinstance(obj, args[0]) return _isinstance(obj, args[0], nested=nested)
return isinstance(obj, get_base_class(cls)) return isinstance(obj, get_base_class(cls))
@ -749,7 +761,7 @@ def check_prop_in_allowed_types(prop: Any, allowed_types: Iterable) -> bool:
""" """
from reflex.vars import Var from reflex.vars import Var
type_ = prop._var_type if _isinstance(prop, Var) else type(prop) type_ = prop._var_type if isinstance(prop, Var) else type(prop)
return type_ in allowed_types return type_ in allowed_types

View File

@ -75,9 +75,9 @@ from reflex.utils.types import (
if TYPE_CHECKING: if TYPE_CHECKING:
from reflex.state import BaseState from reflex.state import BaseState
from .number import BooleanVar, NumberVar from .number import BooleanVar, LiteralBooleanVar, LiteralNumberVar, NumberVar
from .object import ObjectVar from .object import LiteralObjectVar, ObjectVar
from .sequence import ArrayVar, StringVar from .sequence import ArrayVar, LiteralArrayVar, LiteralStringVar, StringVar
VAR_TYPE = TypeVar("VAR_TYPE", covariant=True) VAR_TYPE = TypeVar("VAR_TYPE", covariant=True)
@ -573,13 +573,21 @@ class Var(Generic[VAR_TYPE]):
return value_with_replaced return value_with_replaced
@overload
@classmethod
def create( # pyright: ignore[reportOverlappingOverload]
cls,
value: NoReturn,
_var_data: VarData | None = None,
) -> Var[Any]: ...
@overload @overload
@classmethod @classmethod
def create( # pyright: ignore[reportOverlappingOverload] def create( # pyright: ignore[reportOverlappingOverload]
cls, cls,
value: bool, value: bool,
_var_data: VarData | None = None, _var_data: VarData | None = None,
) -> BooleanVar: ... ) -> LiteralBooleanVar: ...
@overload @overload
@classmethod @classmethod
@ -587,7 +595,7 @@ class Var(Generic[VAR_TYPE]):
cls, cls,
value: int, value: int,
_var_data: VarData | None = None, _var_data: VarData | None = None,
) -> NumberVar[int]: ... ) -> LiteralNumberVar[int]: ...
@overload @overload
@classmethod @classmethod
@ -595,7 +603,15 @@ class Var(Generic[VAR_TYPE]):
cls, cls,
value: float, value: float,
_var_data: VarData | None = None, _var_data: VarData | None = None,
) -> NumberVar[float]: ... ) -> LiteralNumberVar[float]: ...
@overload
@classmethod
def create( # pyright: ignore [reportOverlappingOverload]
cls,
value: str,
_var_data: VarData | None = None,
) -> LiteralStringVar: ...
@overload @overload
@classmethod @classmethod
@ -611,7 +627,7 @@ class Var(Generic[VAR_TYPE]):
cls, cls,
value: None, value: None,
_var_data: VarData | None = None, _var_data: VarData | None = None,
) -> NoneVar: ... ) -> LiteralNoneVar: ...
@overload @overload
@classmethod @classmethod
@ -619,7 +635,7 @@ class Var(Generic[VAR_TYPE]):
cls, cls,
value: MAPPING_TYPE, value: MAPPING_TYPE,
_var_data: VarData | None = None, _var_data: VarData | None = None,
) -> ObjectVar[MAPPING_TYPE]: ... ) -> LiteralObjectVar[MAPPING_TYPE]: ...
@overload @overload
@classmethod @classmethod
@ -627,7 +643,7 @@ class Var(Generic[VAR_TYPE]):
cls, cls,
value: SEQUENCE_TYPE, value: SEQUENCE_TYPE,
_var_data: VarData | None = None, _var_data: VarData | None = None,
) -> ArrayVar[SEQUENCE_TYPE]: ... ) -> LiteralArrayVar[SEQUENCE_TYPE]: ...
@overload @overload
@classmethod @classmethod
@ -935,7 +951,7 @@ class Var(Generic[VAR_TYPE]):
""" """
actual_name = self._var_field_name actual_name = self._var_field_name
def setter(state: BaseState, value: Any): def setter(state: Any, value: Any):
"""Get the setter for the var. """Get the setter for the var.
Args: Args:
@ -953,6 +969,8 @@ class Var(Generic[VAR_TYPE]):
else: else:
setattr(state, actual_name, value) setattr(state, actual_name, value)
setter.__annotations__["value"] = self._var_type
setter.__qualname__ = self._get_setter_name() setter.__qualname__ = self._get_setter_name()
return setter return setter
@ -1885,61 +1903,6 @@ def _or_operation(a: Var, b: Var):
) )
@dataclasses.dataclass(
eq=False,
frozen=True,
slots=True,
)
class CallableVar(Var):
"""Decorate a Var-returning function to act as both a Var and a function.
This is used as a compatibility shim for replacing Var objects in the
API with functions that return a family of Var.
"""
fn: Callable[..., Var] = dataclasses.field(
default_factory=lambda: lambda: Var(_js_expr="undefined")
)
original_var: Var = dataclasses.field(
default_factory=lambda: Var(_js_expr="undefined")
)
def __init__(self, fn: Callable[..., Var]):
"""Initialize a CallableVar.
Args:
fn: The function to decorate (must return Var)
"""
original_var = fn()
super(CallableVar, self).__init__(
_js_expr=original_var._js_expr,
_var_type=original_var._var_type,
_var_data=VarData.merge(original_var._get_all_var_data()),
)
object.__setattr__(self, "fn", fn)
object.__setattr__(self, "original_var", original_var)
def __call__(self, *args: Any, **kwargs: Any) -> Var:
"""Call the decorated function.
Args:
*args: The args to pass to the function.
**kwargs: The kwargs to pass to the function.
Returns:
The Var returned from calling the function.
"""
return self.fn(*args, **kwargs)
def __hash__(self) -> int:
"""Calculate the hash of the object.
Returns:
The hash of the object.
"""
return hash((type(self).__name__, self.original_var))
RETURN_TYPE = TypeVar("RETURN_TYPE") RETURN_TYPE = TypeVar("RETURN_TYPE")
DICT_KEY = TypeVar("DICT_KEY") DICT_KEY = TypeVar("DICT_KEY")
@ -2238,6 +2201,27 @@ class ComputedVar(Var[RETURN_TYPE]):
owner: Type, owner: Type,
) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ... ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ...
@overload
def __get__(
self: ComputedVar[BASE_TYPE],
instance: None,
owner: Type,
) -> ObjectVar[BASE_TYPE]: ...
@overload
def __get__(
self: ComputedVar[SQLA_TYPE],
instance: None,
owner: Type,
) -> ObjectVar[SQLA_TYPE]: ...
if TYPE_CHECKING:
@overload
def __get__(
self: ComputedVar[DATACLASS_TYPE], instance: None, owner: Any
) -> ObjectVar[DATACLASS_TYPE]: ...
@overload @overload
def __get__(self, instance: None, owner: Type) -> ComputedVar[RETURN_TYPE]: ... def __get__(self, instance: None, owner: Type) -> ComputedVar[RETURN_TYPE]: ...
@ -2484,6 +2468,27 @@ class AsyncComputedVar(ComputedVar[RETURN_TYPE]):
owner: Type, owner: Type,
) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ... ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ...
@overload
def __get__(
self: AsyncComputedVar[BASE_TYPE],
instance: None,
owner: Type,
) -> ObjectVar[BASE_TYPE]: ...
@overload
def __get__(
self: AsyncComputedVar[SQLA_TYPE],
instance: None,
owner: Type,
) -> ObjectVar[SQLA_TYPE]: ...
if TYPE_CHECKING:
@overload
def __get__(
self: AsyncComputedVar[DATACLASS_TYPE], instance: None, owner: Any
) -> ObjectVar[DATACLASS_TYPE]: ...
@overload @overload
def __get__(self, instance: None, owner: Type) -> AsyncComputedVar[RETURN_TYPE]: ... def __get__(self, instance: None, owner: Type) -> AsyncComputedVar[RETURN_TYPE]: ...

View File

@ -974,7 +974,7 @@ def boolean_not_operation(value: BooleanVar):
frozen=True, frozen=True,
slots=True, slots=True,
) )
class LiteralNumberVar(LiteralVar, NumberVar): class LiteralNumberVar(LiteralVar, NumberVar[NUMBER_T]):
"""Base class for immutable literal number vars.""" """Base class for immutable literal number vars."""
_var_value: float | int = dataclasses.field(default=0) _var_value: float | int = dataclasses.field(default=0)

View File

@ -22,7 +22,12 @@ from typing_extensions import is_typeddict
from reflex.utils import types from reflex.utils import types
from reflex.utils.exceptions import VarAttributeError from reflex.utils.exceptions import VarAttributeError
from reflex.utils.types import GenericType, get_attribute_access_type, get_origin from reflex.utils.types import (
GenericType,
get_attribute_access_type,
get_origin,
safe_issubclass,
)
from .base import ( from .base import (
CachedVarOperation, CachedVarOperation,
@ -187,10 +192,14 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping):
Returns: Returns:
The item from the object. The item from the object.
""" """
from .sequence import LiteralStringVar
if not isinstance(key, (StringVar, str, int, NumberVar)) or ( if not isinstance(key, (StringVar, str, int, NumberVar)) or (
isinstance(key, NumberVar) and key._is_strict_float() isinstance(key, NumberVar) and key._is_strict_float()
): ):
raise_unsupported_operand_types("[]", (type(self), type(key))) raise_unsupported_operand_types("[]", (type(self), type(key)))
if isinstance(key, str) and isinstance(Var.create(key), LiteralStringVar):
return self.__getattr__(key)
return ObjectItemOperation.create(self, key).guess_type() return ObjectItemOperation.create(self, key).guess_type()
# NoReturn is used here to catch when key value is Any # NoReturn is used here to catch when key value is Any
@ -260,12 +269,12 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping):
if types.is_optional(var_type): if types.is_optional(var_type):
var_type = get_args(var_type)[0] var_type = get_args(var_type)[0]
fixed_type = var_type if isclass(var_type) else get_origin(var_type) fixed_type = get_origin(var_type) or var_type
if ( if (
(isclass(fixed_type) and not issubclass(fixed_type, Mapping)) is_typeddict(fixed_type)
or (isclass(fixed_type) and not safe_issubclass(fixed_type, Mapping))
or (fixed_type in types.UnionTypes) or (fixed_type in types.UnionTypes)
or is_typeddict(fixed_type)
): ):
attribute_type = get_attribute_access_type(var_type, name) attribute_type = get_attribute_access_type(var_type, name)
if attribute_type is None: if attribute_type is None:

View File

@ -372,6 +372,33 @@ class StringVar(Var[STRING_TYPE], python_types=str):
return string_ge_operation(self, other) return string_ge_operation(self, other)
@overload
def replace( # pyright: ignore [reportOverlappingOverload]
self, search_value: StringVar | str, new_value: StringVar | str
) -> StringVar: ...
@overload
def replace(
self, search_value: Any, new_value: Any
) -> CustomVarOperationReturn[StringVar]: ...
def replace(self, search_value: Any, new_value: Any) -> StringVar: # pyright: ignore [reportInconsistentOverload]
"""Replace a string with a value.
Args:
search_value: The string to search.
new_value: The value to be replaced with.
Returns:
The string replace operation.
"""
if not isinstance(search_value, (StringVar, str)):
raise_unsupported_operand_types("replace", (type(self), type(search_value)))
if not isinstance(new_value, (StringVar, str)):
raise_unsupported_operand_types("replace", (type(self), type(new_value)))
return string_replace_operation(self, search_value, new_value)
@var_operation @var_operation
def string_lt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str): def string_lt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
@ -570,7 +597,7 @@ def array_join_operation(array: ArrayVar, sep: StringVar[Any] | str = ""):
@var_operation @var_operation
def string_replace_operation( def string_replace_operation(
string: StringVar, search_value: StringVar | str, new_value: StringVar | str string: StringVar[Any], search_value: StringVar | str, new_value: StringVar | str
): ):
"""Replace a string with a value. """Replace a string with a value.
@ -583,7 +610,7 @@ def string_replace_operation(
The string replace operation. The string replace operation.
""" """
return var_operation_return( return var_operation_return(
js_expression=f"{string}.replace({search_value}, {new_value})", js_expression=f"{string}.replaceAll({search_value}, {new_value})",
var_type=str, var_type=str,
) )

View File

@ -1,3 +1,3 @@
from .fixtures import evaluated_page from .fixtures import evaluated_page, unevaluated_page
__all__ = ["evaluated_page"] __all__ = ["evaluated_page", "unevaluated_page"]

View File

@ -213,6 +213,75 @@ def side_bar():
) )
class NestedElement(rx.Base):
"""A nested element."""
identifier: str
value: list[int]
class BenchmarkState(rx.State):
"""State for the benchmark."""
counter: rx.Field[int] = rx.field(17)
current_key: rx.Field[str] = rx.field("key_2")
@rx.event
def increment(self):
"""Increment the counter."""
self.counter = self.counter + 1
@rx.event
def decrement(self):
"""Decrement the counter."""
self.counter = self.counter - 1
@rx.var
def elements(self) -> list[int]:
"""List of elements.
Returns:
List of elements.
"""
if self.counter < 0:
return list(range(0))
return list(range(self.counter))
@rx.var
def nested_elements(self) -> list[NestedElement]:
"""List of nested elements.
Returns:
List of nested elements.
"""
return [
NestedElement(
identifier=str(i),
value=list(range(i)),
)
for i in range(self.counter)
]
@rx.var
def show_odd(self) -> bool:
"""Check if the counter is odd.
Returns:
True if the counter is odd, False otherwise.
"""
return self.counter % 2 == 1
@rx.var
def show_even(self) -> bool:
"""Check if the counter is even.
Returns:
True if the counter is even, False otherwise.
"""
return self.counter % 2 == 0
LOREM_IPSUM = "Lorem ipsum dolor sit amet, dolor ut dolore pariatur aliqua enim tempor sed. Labore excepteur sed exercitation. Ullamco aliquip lorem sunt enim in incididunt. Magna anim officia sint cillum labore. Ut eu non dolore minim nostrud magna eu, aute ex in incididunt irure eu. Fugiat et magna magna est excepteur eiusmod minim. Quis eiusmod et non pariatur dolor veniam incididunt, eiusmod irure enim sed dolor lorem pariatur do. Occaecat duis irure excepteur dolore. Proident ut laborum pariatur sit sit, nisi nostrud voluptate magna commodo laborum esse velit. Voluptate non minim deserunt adipiscing irure deserunt cupidatat. Laboris veniam commodo incididunt veniam lorem occaecat, fugiat ipsum dolor cupidatat. Ea officia sed eu excepteur culpa adipiscing, tempor consectetur ullamco eu. Anim ex proident nulla sunt culpa, voluptate veniam proident est adipiscing sint elit velit. Laboris adipiscing est culpa cillum magna. Sit veniam nulla nulla, aliqua eiusmod commodo lorem cupidatat commodo occaecat. Fugiat cillum dolor incididunt mollit eiusmod sint. Non lorem dolore labore excepteur minim laborum sed. Irure nisi do lorem nulla sunt commodo, deserunt quis mollit consectetur minim et esse est, proident nostrud officia enim sed reprehenderit. Magna cillum consequat aute reprehenderit duis sunt ullamco. Labore qui mollit voluptate. Duis dolor sint aute amet aliquip officia, est non mollit tempor enim quis fugiat, eu do culpa consectetur magna. Do ullamco aliqua voluptate culpa excepteur reprehenderit reprehenderit. Occaecat nulla sit est magna. Deserunt ea voluptate veniam cillum. Amet cupidatat duis est tempor fugiat ex eu, officia est sunt consectetur labore esse exercitation. Nisi cupidatat irure est nisi. Officia amet eu veniam reprehenderit. In amet incididunt tempor commodo ea labore. Mollit dolor aliquip excepteur, voluptate aute occaecat id officia proident. Ullamco est amet tempor. Proident aliquip proident mollit do aliquip ipsum, culpa quis aute id irure. Velit excepteur cillum cillum ut cupidatat. Occaecat qui elit esse nulla minim. Consequat velit id ad pariatur tempor. Eiusmod deserunt aliqua ex sed quis non. Dolor sint commodo ex in deserunt nostrud excepteur, pariatur ex aliqua anim adipiscing amet proident. Laboris eu laborum magna lorem ipsum fugiat velit." LOREM_IPSUM = "Lorem ipsum dolor sit amet, dolor ut dolore pariatur aliqua enim tempor sed. Labore excepteur sed exercitation. Ullamco aliquip lorem sunt enim in incididunt. Magna anim officia sint cillum labore. Ut eu non dolore minim nostrud magna eu, aute ex in incididunt irure eu. Fugiat et magna magna est excepteur eiusmod minim. Quis eiusmod et non pariatur dolor veniam incididunt, eiusmod irure enim sed dolor lorem pariatur do. Occaecat duis irure excepteur dolore. Proident ut laborum pariatur sit sit, nisi nostrud voluptate magna commodo laborum esse velit. Voluptate non minim deserunt adipiscing irure deserunt cupidatat. Laboris veniam commodo incididunt veniam lorem occaecat, fugiat ipsum dolor cupidatat. Ea officia sed eu excepteur culpa adipiscing, tempor consectetur ullamco eu. Anim ex proident nulla sunt culpa, voluptate veniam proident est adipiscing sint elit velit. Laboris adipiscing est culpa cillum magna. Sit veniam nulla nulla, aliqua eiusmod commodo lorem cupidatat commodo occaecat. Fugiat cillum dolor incididunt mollit eiusmod sint. Non lorem dolore labore excepteur minim laborum sed. Irure nisi do lorem nulla sunt commodo, deserunt quis mollit consectetur minim et esse est, proident nostrud officia enim sed reprehenderit. Magna cillum consequat aute reprehenderit duis sunt ullamco. Labore qui mollit voluptate. Duis dolor sint aute amet aliquip officia, est non mollit tempor enim quis fugiat, eu do culpa consectetur magna. Do ullamco aliqua voluptate culpa excepteur reprehenderit reprehenderit. Occaecat nulla sit est magna. Deserunt ea voluptate veniam cillum. Amet cupidatat duis est tempor fugiat ex eu, officia est sunt consectetur labore esse exercitation. Nisi cupidatat irure est nisi. Officia amet eu veniam reprehenderit. In amet incididunt tempor commodo ea labore. Mollit dolor aliquip excepteur, voluptate aute occaecat id officia proident. Ullamco est amet tempor. Proident aliquip proident mollit do aliquip ipsum, culpa quis aute id irure. Velit excepteur cillum cillum ut cupidatat. Occaecat qui elit esse nulla minim. Consequat velit id ad pariatur tempor. Eiusmod deserunt aliqua ex sed quis non. Dolor sint commodo ex in deserunt nostrud excepteur, pariatur ex aliqua anim adipiscing amet proident. Laboris eu laborum magna lorem ipsum fugiat velit."
@ -233,6 +302,82 @@ def _complicated_page():
) )
@pytest.fixture(params=[_simple_page, _complicated_page]) def _counter():
def evaluated_page(request): return (
rx.text(BenchmarkState.counter),
rx.button("Increment", on_click=BenchmarkState.increment),
rx.button("Decrement", on_click=BenchmarkState.decrement),
rx.cond(
BenchmarkState.counter < 0,
rx.text("Counter is negative"),
rx.fragment(
rx.cond(
BenchmarkState.show_odd,
rx.text("Counter is odd"),
),
rx.cond(
BenchmarkState.show_even,
rx.text("Counter is even"),
),
),
),
)
def _show_key():
return rx.match(
BenchmarkState.current_key,
(
"key_1",
rx.text("Key 1"),
),
(
"key_2",
rx.text("Key 2"),
),
(
"key_3",
rx.text("Key 3"),
),
rx.text("Key not found"),
)
def _simple_foreach():
return rx.foreach(
BenchmarkState.elements,
lambda elem: rx.text(elem),
)
def _render_nested_element(elem: NestedElement, idx):
return (
rx.text(f"{idx} {elem.identifier}"),
rx.foreach(elem.value, lambda value: rx.text(value)),
)
def _nested_foreach():
return rx.foreach(
BenchmarkState.nested_elements,
_render_nested_element,
)
def _stateful_page():
return rx.hstack(
_counter(),
_show_key(),
_simple_foreach(),
_nested_foreach(),
)
@pytest.fixture(params=[_simple_page, _complicated_page, _stateful_page])
def unevaluated_page(request: pytest.FixtureRequest):
return request.param
@pytest.fixture(params=[_simple_page, _complicated_page, _stateful_page])
def evaluated_page(request: pytest.FixtureRequest):
return request.param() return request.param()

View File

@ -1,18 +1,25 @@
import pytest from pytest_codspeed import BenchmarkFixture
from reflex.compiler.compiler import _compile_page, _compile_stateful_components from reflex.compiler.compiler import _compile_page, _compile_stateful_components
from reflex.components.component import Component
@pytest.mark.benchmark def import_templates():
def test_compile_page(evaluated_page): # Importing the templates module to avoid the import time in the benchmark
_compile_page(evaluated_page, None) import reflex.compiler.templates # noqa: F401
@pytest.mark.benchmark def test_compile_page(evaluated_page: Component, benchmark: BenchmarkFixture):
def test_compile_stateful(evaluated_page): import_templates()
_compile_stateful_components([evaluated_page])
benchmark(lambda: _compile_page(evaluated_page, None))
@pytest.mark.benchmark def test_compile_stateful(evaluated_page: Component, benchmark: BenchmarkFixture):
def test_get_all_imports(evaluated_page): import_templates()
evaluated_page._get_all_imports()
benchmark(lambda: _compile_stateful_components([evaluated_page]))
def test_get_all_imports(evaluated_page: Component, benchmark: BenchmarkFixture):
benchmark(lambda: evaluated_page._get_all_imports())

View File

@ -1,9 +1,11 @@
import pytest from typing import Callable
from .fixtures import _complicated_page, _simple_page from pytest_codspeed import BenchmarkFixture
from reflex.components.component import Component
@pytest.mark.benchmark def test_evaluate_page(
@pytest.mark.parametrize("page", [_simple_page, _complicated_page]) unevaluated_page: Callable[[], Component], benchmark: BenchmarkFixture
def test_evaluate_page(page): ):
page() benchmark(unevaluated_page)

View File

@ -20,7 +20,11 @@ def BackgroundTask():
class State(rx.State): class State(rx.State):
counter: int = 0 counter: int = 0
_task_id: int = 0 _task_id: int = 0
iterations: int = 10 iterations: rx.Field[int] = rx.field(10)
@rx.event
def set_iterations(self, value: str):
self.iterations = int(value)
@rx.event(background=True) @rx.event(background=True)
async def handle_event(self): async def handle_event(self):
@ -125,8 +129,8 @@ def BackgroundTask():
rx.input( rx.input(
id="iterations", id="iterations",
placeholder="Iterations", placeholder="Iterations",
value=State.iterations.to_string(), # pyright: ignore [reportAttributeAccessIssue] value=State.iterations.to_string(),
on_change=State.set_iterations, # pyright: ignore [reportAttributeAccessIssue] on_change=State.set_iterations,
), ),
rx.button( rx.button(
"Delayed Increment", "Delayed Increment",

View File

@ -87,7 +87,7 @@ def UploadFile():
), ),
rx.box( rx.box(
rx.foreach( rx.foreach(
rx.selected_files, rx.selected_files(),
lambda f: rx.text(f, as_="p"), lambda f: rx.text(f, as_="p"),
), ),
id="selected_files", id="selected_files",

View File

@ -10,6 +10,8 @@ from reflex.testing import AppHarness
def VarOperations(): def VarOperations():
"""App with var operations.""" """App with var operations."""
from typing import TypedDict
import reflex as rx import reflex as rx
from reflex.vars.base import LiteralVar from reflex.vars.base import LiteralVar
from reflex.vars.sequence import ArrayVar from reflex.vars.sequence import ArrayVar
@ -17,6 +19,10 @@ def VarOperations():
class Object(rx.Base): class Object(rx.Base):
name: str = "hello" name: str = "hello"
class Person(TypedDict):
name: str
age: int
class VarOperationState(rx.State): class VarOperationState(rx.State):
int_var1: rx.Field[int] = rx.field(10) int_var1: rx.Field[int] = rx.field(10)
int_var2: rx.Field[int] = rx.field(5) int_var2: rx.Field[int] = rx.field(5)
@ -34,6 +40,9 @@ def VarOperations():
dict1: rx.Field[dict[int, int]] = rx.field({1: 2}) dict1: rx.Field[dict[int, int]] = rx.field({1: 2})
dict2: rx.Field[dict[int, int]] = rx.field({3: 4}) dict2: rx.Field[dict[int, int]] = rx.field({3: 4})
html_str: rx.Field[str] = rx.field("<div>hello</div>") html_str: rx.Field[str] = rx.field("<div>hello</div>")
people: rx.Field[list[Person]] = rx.field(
[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]
)
app = rx.App(_state=rx.State) app = rx.App(_state=rx.State)
@ -619,6 +628,23 @@ def VarOperations():
), ),
id="dict_in_foreach3", id="dict_in_foreach3",
), ),
rx.box(
rx.foreach("abcdef", lambda x: rx.text.span(x + " ")),
id="str_in_foreach",
),
rx.box(
rx.foreach(VarOperationState.str_var1, lambda x: rx.text.span(x + " ")),
id="str_var_in_foreach",
),
rx.box(
rx.foreach(
VarOperationState.people,
lambda person: rx.text.span(
"Hello " + person["name"], person["age"] + 3
),
),
id="typed_dict_in_foreach",
),
) )
@ -826,6 +852,9 @@ def test_var_operations(driver, var_operations: AppHarness):
("dict_in_foreach1", "a1b2"), ("dict_in_foreach1", "a1b2"),
("dict_in_foreach2", "12"), ("dict_in_foreach2", "12"),
("dict_in_foreach3", "1234"), ("dict_in_foreach3", "1234"),
("str_in_foreach", "a b c d e f"),
("str_var_in_foreach", "f i r s t"),
("typed_dict_in_foreach", "Hello Alice33Hello Bob28"),
] ]
for tag, expected in tests: for tag, expected in tests:

View File

@ -61,7 +61,7 @@ def ColorToggleApp():
rx.icon(tag="moon", size=20), rx.icon(tag="moon", size=20),
value="dark", value="dark",
), ),
on_change=set_color_mode, on_change=set_color_mode(),
variant="classic", variant="classic",
radius="large", radius="large",
value=color_mode, value=color_mode,

View File

@ -11,6 +11,7 @@ from reflex.components.lucide.icon import Icon
from reflex.components.radix.themes.layout.box import Box from reflex.components.radix.themes.layout.box import Box
from reflex.style import Style from reflex.style import Style
from reflex.vars import Var from reflex.vars import Var
from reflex.vars.base import LiteralVar
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -99,7 +100,9 @@ def test_create_shiki_code_block(
applied_styles = component.style applied_styles = component.style
for key, value in expected_styles.items(): for key, value in expected_styles.items():
assert Var.create(applied_styles[key])._var_value == value var = Var.create(applied_styles[key])
assert isinstance(var, LiteralVar)
assert var._var_value == value
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@ -148,7 +148,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe
( (
"code", "code",
{}, {},
"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); const _language = match ? match[1] : ''; if (_language) { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Error importing language module for ${_language}:`, error); } })(); } ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <SyntaxHighlighter children={((Array.isArray(children)) ? children.join("\\n") : children)} css={({ ["marginTop"] : "1em", ["marginBottom"] : "1em" })} customStyle={({ ["marginTop"] : "1em", ["marginBottom"] : "1em" })} language={_language} style={((resolvedColorMode === "light") ? oneLight : oneDark)} wrapLongLines={true} {...props}/> ); })""", r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); let _language = match ? match[1] : ''; if (_language) { if (!["abap", "abnf", "actionscript", "ada", "agda", "al", "antlr4", "apacheconf", "apex", "apl", "applescript", "aql", "arduino", "arff", "asciidoc", "asm6502", "asmatmel", "aspnet", "autohotkey", "autoit", "avisynth", "avro-idl", "bash", "basic", "batch", "bbcode", "bicep", "birb", "bison", "bnf", "brainfuck", "brightscript", "bro", "bsl", "c", "cfscript", "chaiscript", "cil", "clike", "clojure", "cmake", "cobol", "coffeescript", "concurnas", "coq", "core", "cpp", "crystal", "csharp", "cshtml", "csp", "css", "css-extras", "csv", "cypher", "d", "dart", "dataweave", "dax", "dhall", "diff", "django", "dns-zone-file", "docker", "dot", "ebnf", "editorconfig", "eiffel", "ejs", "elixir", "elm", "erb", "erlang", "etlua", "excel-formula", "factor", "false", "firestore-security-rules", "flow", "fortran", "fsharp", "ftl", "gap", "gcode", "gdscript", "gedcom", "gherkin", "git", "glsl", "gml", "gn", "go", "go-module", "graphql", "groovy", "haml", "handlebars", "haskell", "haxe", "hcl", "hlsl", "hoon", "hpkp", "hsts", "http", "ichigojam", "icon", "icu-message-format", "idris", "iecst", "ignore", "index", "inform7", "ini", "io", "j", "java", "javadoc", "javadoclike", "javascript", "javastacktrace", "jexl", "jolie", "jq", "js-extras", "js-templates", "jsdoc", "json", "json5", "jsonp", "jsstacktrace", "jsx", "julia", "keepalived", "keyman", "kotlin", "kumir", "kusto", "latex", "latte", "less", "lilypond", "liquid", "lisp", "livescript", "llvm", "log", "lolcode", "lua", "magma", "makefile", "markdown", "markup", "markup-templating", "matlab", "maxscript", "mel", "mermaid", "mizar", "mongodb", "monkey", "moonscript", "n1ql", "n4js", "nand2tetris-hdl", "naniscript", "nasm", "neon", "nevod", "nginx", "nim", "nix", "nsis", "objectivec", "ocaml", "opencl", "openqasm", "oz", "parigp", "parser", "pascal", "pascaligo", "pcaxis", "peoplecode", "perl", "php", "php-extras", "phpdoc", "plsql", "powerquery", "powershell", "processing", "prolog", "promql", "properties", "protobuf", "psl", "pug", "puppet", "pure", "purebasic", "purescript", "python", "q", "qml", "qore", "qsharp", "r", "racket", "reason", "regex", "rego", "renpy", "rest", "rip", "roboconf", "robotframework", "ruby", "rust", "sas", "sass", "scala", "scheme", "scss", "shell-session", "smali", "smalltalk", "smarty", "sml", "solidity", "solution-file", "soy", "sparql", "splunk-spl", "sqf", "sql", "squirrel", "stan", "stylus", "swift", "systemd", "t4-cs", "t4-templating", "t4-vb", "tap", "tcl", "textile", "toml", "tremor", "tsx", "tt2", "turtle", "twig", "typescript", "typoscript", "unrealscript", "uorazor", "uri", "v", "vala", "vbnet", "velocity", "verilog", "vhdl", "vim", "visual-basic", "warpscript", "wasm", "web-idl", "wiki", "wolfram", "wren", "xeora", "xml-doc", "xojo", "xquery", "yaml", "yang", "zig"].includes(_language)) { console.warn(`Language \`${_language}\` is not supported for code blocks inside of markdown.`); _language = ''; } else { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Language ${_language} is not supported for code blocks inside of markdown: `, error); } })(); } } ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <SyntaxHighlighter children={((Array.isArray(children)) ? children.join("\n") : children)} css={({ ["marginTop"] : "1em", ["marginBottom"] : "1em" })} customStyle={({ ["marginTop"] : "1em", ["marginBottom"] : "1em" })} language={_language} style={((resolvedColorMode === "light") ? oneLight : oneDark)} wrapLongLines={true} {...props}/> ); })""",
), ),
( (
"code", "code",
@ -157,7 +157,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe
value, **props value, **props
) )
}, },
"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); const _language = match ? match[1] : ''; ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <RadixThemesBox css={({ ["pre"] : ({ ["margin"] : "0", ["padding"] : "24px", ["background"] : "transparent", ["overflow-x"] : "auto", ["border-radius"] : "6px" }) })} {...props}><ShikiCode code={((Array.isArray(children)) ? children.join("\\n") : children)} decorations={[]} language={_language} theme={((resolvedColorMode === "light") ? "one-light" : "one-dark-pro")} transformers={[]}/></RadixThemesBox> ); })""", r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); let _language = match ? match[1] : ''; ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <RadixThemesBox css={({ ["pre"] : ({ ["margin"] : "0", ["padding"] : "24px", ["background"] : "transparent", ["overflow-x"] : "auto", ["border-radius"] : "6px" }) })} {...props}><ShikiCode code={((Array.isArray(children)) ? children.join("\n") : children)} decorations={[]} language={_language} theme={((resolvedColorMode === "light") ? "one-light" : "one-dark-pro")} transformers={[]}/></RadixThemesBox> ); })""",
), ),
( (
"h1", "h1",
@ -171,7 +171,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe
( (
"code", "code",
{"codeblock": syntax_highlighter_memoized_component(CodeBlock)}, {"codeblock": syntax_highlighter_memoized_component(CodeBlock)},
"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); const _language = match ? match[1] : ''; if (_language) { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Error importing language module for ${_language}:`, error); } })(); } ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <CodeBlock code={((Array.isArray(children)) ? children.join("\\n") : children)} language={_language} {...props}/> ); })""", r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); let _language = match ? match[1] : ''; if (_language) { if (!["abap", "abnf", "actionscript", "ada", "agda", "al", "antlr4", "apacheconf", "apex", "apl", "applescript", "aql", "arduino", "arff", "asciidoc", "asm6502", "asmatmel", "aspnet", "autohotkey", "autoit", "avisynth", "avro-idl", "bash", "basic", "batch", "bbcode", "bicep", "birb", "bison", "bnf", "brainfuck", "brightscript", "bro", "bsl", "c", "cfscript", "chaiscript", "cil", "clike", "clojure", "cmake", "cobol", "coffeescript", "concurnas", "coq", "core", "cpp", "crystal", "csharp", "cshtml", "csp", "css", "css-extras", "csv", "cypher", "d", "dart", "dataweave", "dax", "dhall", "diff", "django", "dns-zone-file", "docker", "dot", "ebnf", "editorconfig", "eiffel", "ejs", "elixir", "elm", "erb", "erlang", "etlua", "excel-formula", "factor", "false", "firestore-security-rules", "flow", "fortran", "fsharp", "ftl", "gap", "gcode", "gdscript", "gedcom", "gherkin", "git", "glsl", "gml", "gn", "go", "go-module", "graphql", "groovy", "haml", "handlebars", "haskell", "haxe", "hcl", "hlsl", "hoon", "hpkp", "hsts", "http", "ichigojam", "icon", "icu-message-format", "idris", "iecst", "ignore", "index", "inform7", "ini", "io", "j", "java", "javadoc", "javadoclike", "javascript", "javastacktrace", "jexl", "jolie", "jq", "js-extras", "js-templates", "jsdoc", "json", "json5", "jsonp", "jsstacktrace", "jsx", "julia", "keepalived", "keyman", "kotlin", "kumir", "kusto", "latex", "latte", "less", "lilypond", "liquid", "lisp", "livescript", "llvm", "log", "lolcode", "lua", "magma", "makefile", "markdown", "markup", "markup-templating", "matlab", "maxscript", "mel", "mermaid", "mizar", "mongodb", "monkey", "moonscript", "n1ql", "n4js", "nand2tetris-hdl", "naniscript", "nasm", "neon", "nevod", "nginx", "nim", "nix", "nsis", "objectivec", "ocaml", "opencl", "openqasm", "oz", "parigp", "parser", "pascal", "pascaligo", "pcaxis", "peoplecode", "perl", "php", "php-extras", "phpdoc", "plsql", "powerquery", "powershell", "processing", "prolog", "promql", "properties", "protobuf", "psl", "pug", "puppet", "pure", "purebasic", "purescript", "python", "q", "qml", "qore", "qsharp", "r", "racket", "reason", "regex", "rego", "renpy", "rest", "rip", "roboconf", "robotframework", "ruby", "rust", "sas", "sass", "scala", "scheme", "scss", "shell-session", "smali", "smalltalk", "smarty", "sml", "solidity", "solution-file", "soy", "sparql", "splunk-spl", "sqf", "sql", "squirrel", "stan", "stylus", "swift", "systemd", "t4-cs", "t4-templating", "t4-vb", "tap", "tcl", "textile", "toml", "tremor", "tsx", "tt2", "turtle", "twig", "typescript", "typoscript", "unrealscript", "uorazor", "uri", "v", "vala", "vbnet", "velocity", "verilog", "vhdl", "vim", "visual-basic", "warpscript", "wasm", "web-idl", "wiki", "wolfram", "wren", "xeora", "xml-doc", "xojo", "xquery", "yaml", "yang", "zig"].includes(_language)) { console.warn(`Language \`${_language}\` is not supported for code blocks inside of markdown.`); _language = ''; } else { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Language ${_language} is not supported for code blocks inside of markdown: `, error); } })(); } } ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <CodeBlock code={((Array.isArray(children)) ? children.join("\n") : children)} language={_language} {...props}/> ); })""",
), ),
( (
"code", "code",
@ -180,11 +180,12 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe
ShikiHighLevelCodeBlock ShikiHighLevelCodeBlock
) )
}, },
"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); const _language = match ? match[1] : ''; ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <CodeBlock code={((Array.isArray(children)) ? children.join("\\n") : children)} language={_language} {...props}/> ); })""", r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); let _language = match ? match[1] : ''; ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <CodeBlock code={((Array.isArray(children)) ? children.join("\n") : children)} language={_language} {...props}/> ); })""",
), ),
], ],
) )
def test_markdown_format_component(key, component_map, expected): def test_markdown_format_component(key, component_map, expected):
markdown = Markdown.create("# header", component_map=component_map) markdown = Markdown.create("# header", component_map=component_map)
result = markdown.format_component_map() result = markdown.format_component_map()
print(str(result[key]))
assert str(result[key]) == expected assert str(result[key]) == expected

View File

@ -1299,6 +1299,7 @@ def test_app_wrap_compile_theme(
app_js_lines = [ app_js_lines = [
line.strip() for line in app_js_contents.splitlines() if line.strip() line.strip() for line in app_js_contents.splitlines() if line.strip()
] ]
lines = "".join(app_js_lines)
assert ( assert (
"function AppWrap({children}) {" "function AppWrap({children}) {"
"return (" "return ("
@ -1306,14 +1307,17 @@ def test_app_wrap_compile_theme(
+ "<RadixThemesColorModeProvider>" + "<RadixThemesColorModeProvider>"
"<RadixThemesTheme accentColor={\"plum\"} css={{...theme.styles.global[':root'], ...theme.styles.global.body}}>" "<RadixThemesTheme accentColor={\"plum\"} css={{...theme.styles.global[':root'], ...theme.styles.global.body}}>"
"<Fragment>" "<Fragment>"
"<MemoizedToastProvider/>"
"<Fragment>"
"{children}" "{children}"
"</Fragment>" "</Fragment>"
"</Fragment>"
"</RadixThemesTheme>" "</RadixThemesTheme>"
"</RadixThemesColorModeProvider>" "</RadixThemesColorModeProvider>"
+ ("</StrictMode>" if react_strict_mode else "") + ("</StrictMode>" if react_strict_mode else "")
+ ")" + ")"
"}" "}"
) in "".join(app_js_lines) ) in lines
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -1362,6 +1366,7 @@ def test_app_wrap_priority(
app_js_lines = [ app_js_lines = [
line.strip() for line in app_js_contents.splitlines() if line.strip() line.strip() for line in app_js_contents.splitlines() if line.strip()
] ]
lines = "".join(app_js_lines)
assert ( assert (
"function AppWrap({children}) {" "function AppWrap({children}) {"
"return (" + ("<StrictMode>" if react_strict_mode else "") + "<RadixThemesBox>" "return (" + ("<StrictMode>" if react_strict_mode else "") + "<RadixThemesBox>"
@ -1369,14 +1374,16 @@ def test_app_wrap_priority(
"<RadixThemesColorModeProvider>" "<RadixThemesColorModeProvider>"
"<Fragment2>" "<Fragment2>"
"<Fragment>" "<Fragment>"
"<MemoizedToastProvider/>"
"<Fragment>"
"{children}" "{children}"
"</Fragment>" "</Fragment>"
"</Fragment>"
"</Fragment2>" "</Fragment2>"
"</RadixThemesColorModeProvider>" "</RadixThemesColorModeProvider>"
"</RadixThemesText>" "</RadixThemesText>"
"</RadixThemesBox>" + ("</StrictMode>" if react_strict_mode else "") + ")" "</RadixThemesBox>" + ("</StrictMode>" if react_strict_mode else "")
"}" ) in lines
) in "".join(app_js_lines)
def test_app_state_determination(): def test_app_state_determination():

View File

@ -252,6 +252,7 @@ def test_env_var():
BLUBB: EnvVar[str] = env_var("default") BLUBB: EnvVar[str] = env_var("default")
INTERNAL: EnvVar[str] = env_var("default", internal=True) INTERNAL: EnvVar[str] = env_var("default", internal=True)
BOOLEAN: EnvVar[bool] = env_var(False) BOOLEAN: EnvVar[bool] = env_var(False)
LIST: EnvVar[list[int]] = env_var([1, 2, 3])
assert TestEnv.BLUBB.get() == "default" assert TestEnv.BLUBB.get() == "default"
assert TestEnv.BLUBB.name == "BLUBB" assert TestEnv.BLUBB.name == "BLUBB"
@ -280,3 +281,11 @@ def test_env_var():
assert TestEnv.BOOLEAN.get() is False assert TestEnv.BOOLEAN.get() is False
TestEnv.BOOLEAN.set(None) TestEnv.BOOLEAN.set(None)
assert "BOOLEAN" not in os.environ assert "BOOLEAN" not in os.environ
assert TestEnv.LIST.get() == [1, 2, 3]
assert TestEnv.LIST.name == "LIST"
TestEnv.LIST.set([4, 5, 6])
assert os.environ.get("LIST") == "4:5:6"
assert TestEnv.LIST.get() == [4, 5, 6]
TestEnv.LIST.set(None)
assert "LIST" not in os.environ

View File

@ -8,6 +8,7 @@ from pandas import DataFrame
import reflex as rx import reflex as rx
from reflex.base import Base from reflex.base import Base
from reflex.config import PerformanceMode
from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
from reflex.state import BaseState from reflex.state import BaseState
from reflex.utils.exceptions import ( from reflex.utils.exceptions import (
@ -1893,3 +1894,27 @@ def test_var_data_hooks():
def test_var_data_with_hooks_value(): def test_var_data_with_hooks_value():
var_data = VarData(hooks={"what": VarData(hooks={"whot": VarData(hooks="whott")})}) var_data = VarData(hooks={"what": VarData(hooks={"whot": VarData(hooks="whott")})})
assert var_data == VarData(hooks=["what", "whot", "whott"]) assert var_data == VarData(hooks=["what", "whot", "whott"])
def test_str_var_in_components(mocker):
class StateWithVar(rx.State):
field: int = 1
mocker.patch(
"reflex.components.base.bare.get_performance_mode",
return_value=PerformanceMode.RAISE,
)
with pytest.raises(ValueError):
rx.vstack(
str(StateWithVar.field),
)
mocker.patch(
"reflex.components.base.bare.get_performance_mode",
return_value=PerformanceMode.OFF,
)
rx.vstack(
str(StateWithVar.field),
)

View File

@ -115,7 +115,20 @@ def test_typehint_issubclass(subclass, superclass, expected):
assert types.typehint_issubclass(subclass, superclass) == expected assert types.typehint_issubclass(subclass, superclass) == expected
def test_validate_invalid_bun_path(mocker): def test_validate_none_bun_path(mocker):
"""Test that an error is thrown when a bun path is not specified.
Args:
mocker: Pytest mocker object.
"""
mocker.patch("reflex.utils.path_ops.get_bun_path", return_value=None)
# with pytest.raises(typer.Exit):
prerequisites.validate_bun()
def test_validate_invalid_bun_path(
mocker,
):
"""Test that an error is thrown when a custom specified bun path is not valid """Test that an error is thrown when a custom specified bun path is not valid
or does not exist. or does not exist.
@ -123,13 +136,12 @@ def test_validate_invalid_bun_path(mocker):
mocker: Pytest mocker object. mocker: Pytest mocker object.
""" """
mock_path = mocker.Mock() mock_path = mocker.Mock()
mock_path.samefile.return_value = False
mocker.patch("reflex.utils.path_ops.get_bun_path", return_value=mock_path) mocker.patch("reflex.utils.path_ops.get_bun_path", return_value=mock_path)
mocker.patch("reflex.utils.path_ops.samefile", return_value=False)
mocker.patch("reflex.utils.prerequisites.get_bun_version", return_value=None) mocker.patch("reflex.utils.prerequisites.get_bun_version", return_value=None)
with pytest.raises(typer.Exit): with pytest.raises(typer.Exit):
prerequisites.validate_bun() prerequisites.validate_bun()
mock_path.samefile.assert_called_once()
def test_validate_bun_path_incompatible_version(mocker): def test_validate_bun_path_incompatible_version(mocker):
@ -141,6 +153,7 @@ def test_validate_bun_path_incompatible_version(mocker):
mock_path = mocker.Mock() mock_path = mocker.Mock()
mock_path.samefile.return_value = False mock_path.samefile.return_value = False
mocker.patch("reflex.utils.path_ops.get_bun_path", return_value=mock_path) mocker.patch("reflex.utils.path_ops.get_bun_path", return_value=mock_path)
mocker.patch("reflex.utils.path_ops.samefile", return_value=False)
mocker.patch( mocker.patch(
"reflex.utils.prerequisites.get_bun_version", "reflex.utils.prerequisites.get_bun_version",
return_value=version.parse("0.6.5"), return_value=version.parse("0.6.5"),

View File

@ -74,11 +74,11 @@ class ObjectState(rx.State):
@pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass]) @pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass])
def test_var_create(type_: GenericType) -> None: def test_var_create(type_: type[Base | Bare | SqlaModel | Dataclass]) -> None:
my_object = type_() my_object = type_()
var = Var.create(my_object) var = Var.create(my_object)
assert var._var_type is type_ assert var._var_type is type_
assert isinstance(var, ObjectVar)
quantity = var.quantity quantity = var.quantity
assert quantity._var_type is int assert quantity._var_type is int
@ -94,12 +94,12 @@ def test_literal_create(type_: GenericType) -> None:
@pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass]) @pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass])
def test_guess(type_: GenericType) -> None: def test_guess(type_: type[Base | Bare | SqlaModel | Dataclass]) -> None:
my_object = type_() my_object = type_()
var = Var.create(my_object) var = Var.create(my_object)
var = var.guess_type() var = var.guess_type()
assert var._var_type is type_ assert var._var_type is type_
assert isinstance(var, ObjectVar)
quantity = var.quantity quantity = var.quantity
assert quantity._var_type is int assert quantity._var_type is int