Merge remote-tracking branch 'origin/main' into masenf/cache-stateful-imports
This commit is contained in:
commit
5950336745
2
.github/codeql-config.yml
vendored
Normal file
2
.github/codeql-config.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
paths-ignore:
|
||||
- "**/tests/**"
|
50
.github/workflows/benchmarks.yml
vendored
50
.github/workflows/benchmarks.yml
vendored
@ -70,56 +70,6 @@ jobs:
|
||||
env:
|
||||
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)
|
||||
if: github.event.pull_request.merged == true
|
||||
timeout-minutes: 30
|
||||
|
103
.github/workflows/codeql.yml
vendored
Normal file
103
.github/workflows/codeql.yml
vendored
Normal 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}}"
|
32
.github/workflows/integration_tests.yml
vendored
32
.github/workflows/integration_tests.yml
vendored
@ -94,26 +94,6 @@ jobs:
|
||||
# Check that npm is home
|
||||
npm -v
|
||||
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
|
||||
working-directory: ./reflex-examples/nba-proxy
|
||||
run: |
|
||||
@ -174,12 +154,6 @@ jobs:
|
||||
# Check that npm is home
|
||||
npm -v
|
||||
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:
|
||||
strategy:
|
||||
@ -243,9 +217,3 @@ jobs:
|
||||
# Check that npm is home
|
||||
npm -v
|
||||
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
|
||||
|
@ -3,7 +3,7 @@ fail_fast: true
|
||||
repos:
|
||||
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.9.3
|
||||
rev: v0.9.6
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
args: [reflex, tests]
|
||||
|
@ -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)
|
@ -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
395
poetry.lock
generated
@ -403,75 +403,76 @@ markers = {main = "(platform_system == \"Windows\" or os_name == \"nt\") and (py
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.6.10"
|
||||
version = "7.6.12"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
||||
files = [
|
||||
{file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"},
|
||||
{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.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.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"},
|
||||
{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.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.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"},
|
||||
{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.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.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"},
|
||||
{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.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.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"},
|
||||
{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.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.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"},
|
||||
{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.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.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"},
|
||||
{file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"},
|
||||
{file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"},
|
||||
{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.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.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"},
|
||||
{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.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.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"},
|
||||
{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.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.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"},
|
||||
{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.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.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"},
|
||||
{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.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.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"},
|
||||
{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.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.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"},
|
||||
{file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"},
|
||||
{file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"},
|
||||
{file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -482,40 +483,44 @@ toml = ["tomli"]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "44.0.0"
|
||||
version = "44.0.1"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
optional = false
|
||||
python-versions = "!=3.9.0,!=3.9.1,>=3.7"
|
||||
groups = ["main"]
|
||||
markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and (python_version <= \"3.11\" or python_version >= \"3.12\")"
|
||||
files = [
|
||||
{file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"},
|
||||
{file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"},
|
||||
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"},
|
||||
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"},
|
||||
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"},
|
||||
{file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"},
|
||||
{file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"},
|
||||
{file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-win32.whl", hash = "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-win32.whl", hash = "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12"},
|
||||
{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]
|
||||
@ -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)"]
|
||||
sdist = ["build (>=1.0.0)"]
|
||||
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"]
|
||||
|
||||
[[package]]
|
||||
@ -852,15 +857,15 @@ test = ["coverage[toml]", "pretend", "pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.6.6"
|
||||
version = "2.6.7"
|
||||
description = "File identification library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
||||
files = [
|
||||
{file = "identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881"},
|
||||
{file = "identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251"},
|
||||
{file = "identify-2.6.7-py2.py3-none-any.whl", hash = "sha256:155931cb617a401807b09ecec6635d6c692d180090a1cedca8ef7d58ba5b6aa0"},
|
||||
{file = "identify-2.6.7.tar.gz", hash = "sha256:3fa266b42eba321ee0b2bb0936a6a6b9e36a1351cbb69055b3082f4193035684"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@ -1074,15 +1079,15 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
|
||||
|
||||
[[package]]
|
||||
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."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
||||
files = [
|
||||
{file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627"},
|
||||
{file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8"},
|
||||
{file = "Mako-1.3.9-py3-none-any.whl", hash = "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1"},
|
||||
{file = "mako-1.3.9.tar.gz", hash = "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1558,25 +1563,25 @@ type = ["mypy (>=1.11.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "playwright"
|
||||
version = "1.49.1"
|
||||
version = "1.50.0"
|
||||
description = "A high-level API to automate web browsers"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
||||
files = [
|
||||
{file = "playwright-1.49.1-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:1041ffb45a0d0bc44d698d3a5aa3ac4b67c9bd03540da43a0b70616ad52592b8"},
|
||||
{file = "playwright-1.49.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9f38ed3d0c1f4e0a6d1c92e73dd9a61f8855133249d6f0cec28648d38a7137be"},
|
||||
{file = "playwright-1.49.1-py3-none-macosx_11_0_universal2.whl", hash = "sha256:3be48c6d26dc819ca0a26567c1ae36a980a0303dcd4249feb6f59e115aaddfb8"},
|
||||
{file = "playwright-1.49.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:753ca90ee31b4b03d165cfd36e477309ebf2b4381953f2a982ff612d85b147d2"},
|
||||
{file = "playwright-1.49.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd9bc8dab37aa25198a01f555f0a2e2c3813fe200fef018ac34dfe86b34994b9"},
|
||||
{file = "playwright-1.49.1-py3-none-win32.whl", hash = "sha256:43b304be67f096058e587dac453ece550eff87b8fbed28de30f4f022cc1745bb"},
|
||||
{file = "playwright-1.49.1-py3-none-win_amd64.whl", hash = "sha256:47b23cb346283278f5b4d1e1990bcb6d6302f80c0aa0ca93dd0601a1400191df"},
|
||||
{file = "playwright-1.50.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:f36d754a6c5bd9bf7f14e8f57a2aea6fd08f39ca4c8476481b9c83e299531148"},
|
||||
{file = "playwright-1.50.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:40f274384591dfd27f2b014596250b2250c843ed1f7f4ef5d2960ecb91b4961e"},
|
||||
{file = "playwright-1.50.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:9922ef9bcd316995f01e220acffd2d37a463b4ad10fd73e388add03841dfa230"},
|
||||
{file = "playwright-1.50.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:8fc628c492d12b13d1f347137b2ac6c04f98197ff0985ef0403a9a9ee0d39131"},
|
||||
{file = "playwright-1.50.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcff35f72db2689a79007aee78f1b0621a22e6e3d6c1f58aaa9ac805bf4497c"},
|
||||
{file = "playwright-1.50.0-py3-none-win32.whl", hash = "sha256:3b906f4d351260016a8c5cc1e003bb341651ae682f62213b50168ed581c7558a"},
|
||||
{file = "playwright-1.50.0-py3-none-win_amd64.whl", hash = "sha256:1859423da82de631704d5e3d88602d755462b0906824c1debe140979397d2e8d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
greenlet = "3.1.1"
|
||||
pyee = "12.0.0"
|
||||
greenlet = ">=3.1.1,<4.0.0"
|
||||
pyee = ">=12,<13"
|
||||
|
||||
[[package]]
|
||||
name = "plotly"
|
||||
@ -1828,15 +1833,15 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
||||
files = [
|
||||
{file = "pyee-12.0.0-py3-none-any.whl", hash = "sha256:7b14b74320600049ccc7d0e0b1becd3b4bd0a03c745758225e31a59f4095c990"},
|
||||
{file = "pyee-12.0.0.tar.gz", hash = "sha256:c480603f4aa2927d4766eb41fa82793fe60a82cbfdb8d688e0d08c55a534e145"},
|
||||
{file = "pyee-12.1.1-py3-none-any.whl", hash = "sha256:18a19c650556bb6b32b406d7f017c8f513aceed1ef7ca618fb65de7bd2d347ef"},
|
||||
{file = "pyee-12.1.1.tar.gz", hash = "sha256:bbc33c09e2ff827f74191e3e5bbc6be7da02f627b7ec30d86f5ce1a6fb2424a3"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -2311,15 +2316,15 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"
|
||||
|
||||
[[package]]
|
||||
name = "reflex-hosting-cli"
|
||||
version = "0.1.34"
|
||||
version = "0.1.35"
|
||||
description = "Reflex Hosting CLI"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
groups = ["main"]
|
||||
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
||||
files = [
|
||||
{file = "reflex_hosting_cli-0.1.34-py3-none-any.whl", hash = "sha256:eabc4dc7bf68e022a9388614c1a35b5ab36b01021df063d0c3356eda0e245264"},
|
||||
{file = "reflex_hosting_cli-0.1.34.tar.gz", hash = "sha256:07be37fda6dcede0a5d4bc1fd1786d9a3df5ad4e49dc1b6ba335418563cfecec"},
|
||||
{file = "reflex_hosting_cli-0.1.35-py3-none-any.whl", hash = "sha256:619687be27e6691cb54f6cf038e98d4d622fcf25a85bc9986f8daf52b48e6744"},
|
||||
{file = "reflex_hosting_cli-0.1.35.tar.gz", hash = "sha256:9a5d02978b900045464a1a5581f3adc6260daaa09e8acf95fd05024cda926ae7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -2410,31 +2415,31 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.9.3"
|
||||
version = "0.9.6"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
||||
files = [
|
||||
{file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"},
|
||||
{file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"},
|
||||
{file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"},
|
||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"},
|
||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"},
|
||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"},
|
||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"},
|
||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"},
|
||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"},
|
||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"},
|
||||
{file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"},
|
||||
{file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"},
|
||||
{file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"},
|
||||
{file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"},
|
||||
{file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"},
|
||||
{file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"},
|
||||
{file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"},
|
||||
{file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"},
|
||||
{file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"},
|
||||
{file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"},
|
||||
{file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"},
|
||||
{file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"},
|
||||
{file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"},
|
||||
{file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"},
|
||||
{file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"},
|
||||
{file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"},
|
||||
{file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"},
|
||||
{file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"},
|
||||
{file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2571,70 +2576,70 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.37"
|
||||
version = "2.0.38"
|
||||
description = "Database Abstraction Library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
||||
files = [
|
||||
{file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da36c3b0e891808a7542c5c89f224520b9a16c7f5e4d6a1156955605e54aef0e"},
|
||||
{file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e7402ff96e2b073a98ef6d6142796426d705addd27b9d26c3b32dbaa06d7d069"},
|
||||
{file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6f5d254a22394847245f411a2956976401e84da4288aa70cbcd5190744062c1"},
|
||||
{file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41296bbcaa55ef5fdd32389a35c710133b097f7b2609d8218c0eabded43a1d84"},
|
||||
{file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bedee60385c1c0411378cbd4dc486362f5ee88deceea50002772912d798bb00f"},
|
||||
{file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6c67415258f9f3c69867ec02fea1bf6508153709ecbd731a982442a590f2b7e4"},
|
||||
{file = "SQLAlchemy-2.0.37-cp310-cp310-win32.whl", hash = "sha256:650dcb70739957a492ad8acff65d099a9586b9b8920e3507ca61ec3ce650bb72"},
|
||||
{file = "SQLAlchemy-2.0.37-cp310-cp310-win_amd64.whl", hash = "sha256:93d1543cd8359040c02b6614421c8e10cd7a788c40047dbc507ed46c29ae5636"},
|
||||
{file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:78361be6dc9073ed17ab380985d1e45e48a642313ab68ab6afa2457354ff692c"},
|
||||
{file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b661b49d0cb0ab311a189b31e25576b7ac3e20783beb1e1817d72d9d02508bf5"},
|
||||
{file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d57bafbab289e147d064ffbd5cca2d7b1394b63417c0636cea1f2e93d16eb9e8"},
|
||||
{file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa2c0913f02341d25fb858e4fb2031e6b0813494cca1ba07d417674128ce11b"},
|
||||
{file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9df21b8d9e5c136ea6cde1c50d2b1c29a2b5ff2b1d610165c23ff250e0704087"},
|
||||
{file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db18ff6b8c0f1917f8b20f8eca35c28bbccb9f83afa94743e03d40203ed83de9"},
|
||||
{file = "SQLAlchemy-2.0.37-cp311-cp311-win32.whl", hash = "sha256:46954173612617a99a64aee103bcd3f078901b9a8dcfc6ae80cbf34ba23df989"},
|
||||
{file = "SQLAlchemy-2.0.37-cp311-cp311-win_amd64.whl", hash = "sha256:7b7e772dc4bc507fdec4ee20182f15bd60d2a84f1e087a8accf5b5b7a0dcf2ba"},
|
||||
{file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2952748ecd67ed3b56773c185e85fc084f6bdcdec10e5032a7c25a6bc7d682ef"},
|
||||
{file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3151822aa1db0eb5afd65ccfafebe0ef5cda3a7701a279c8d0bf17781a793bb4"},
|
||||
{file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaa8039b6d20137a4e02603aba37d12cd2dde7887500b8855356682fc33933f4"},
|
||||
{file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdba1f73b64530c47b27118b7053b8447e6d6f3c8104e3ac59f3d40c33aa9fd"},
|
||||
{file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1b2690456528a87234a75d1a1644cdb330a6926f455403c8e4f6cad6921f9098"},
|
||||
{file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf5ae8a9dcf657fd72144a7fd01f243236ea39e7344e579a121c4205aedf07bb"},
|
||||
{file = "SQLAlchemy-2.0.37-cp312-cp312-win32.whl", hash = "sha256:ea308cec940905ba008291d93619d92edaf83232ec85fbd514dcb329f3192761"},
|
||||
{file = "SQLAlchemy-2.0.37-cp312-cp312-win_amd64.whl", hash = "sha256:635d8a21577341dfe4f7fa59ec394b346da12420b86624a69e466d446de16aff"},
|
||||
{file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c4096727193762e72ce9437e2a86a110cf081241919ce3fab8e89c02f6b6658"},
|
||||
{file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e4fb5ac86d8fe8151966814f6720996430462e633d225497566b3996966b9bdb"},
|
||||
{file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e56a139bfe136a22c438478a86f8204c1eb5eed36f4e15c4224e4b9db01cb3e4"},
|
||||
{file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f95fc8e3f34b5f6b3effb49d10ac97c569ec8e32f985612d9b25dd12d0d2e94"},
|
||||
{file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c505edd429abdfe3643fa3b2e83efb3445a34a9dc49d5f692dd087be966020e0"},
|
||||
{file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:12b0f1ec623cccf058cf21cb544f0e74656618165b083d78145cafde156ea7b6"},
|
||||
{file = "SQLAlchemy-2.0.37-cp313-cp313-win32.whl", hash = "sha256:293f9ade06b2e68dd03cfb14d49202fac47b7bb94bffcff174568c951fbc7af2"},
|
||||
{file = "SQLAlchemy-2.0.37-cp313-cp313-win_amd64.whl", hash = "sha256:d70f53a0646cc418ca4853da57cf3ddddbccb8c98406791f24426f2dd77fd0e2"},
|
||||
{file = "SQLAlchemy-2.0.37-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44f569d0b1eb82301b92b72085583277316e7367e038d97c3a1a899d9a05e342"},
|
||||
{file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2eae3423e538c10d93ae3e87788c6a84658c3ed6db62e6a61bb9495b0ad16bb"},
|
||||
{file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfff7be361048244c3aa0f60b5e63221c5e0f0e509f4e47b8910e22b57d10ae7"},
|
||||
{file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:5bc3339db84c5fb9130ac0e2f20347ee77b5dd2596ba327ce0d399752f4fce39"},
|
||||
{file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:84b9f23b0fa98a6a4b99d73989350a94e4a4ec476b9a7dfe9b79ba5939f5e80b"},
|
||||
{file = "SQLAlchemy-2.0.37-cp37-cp37m-win32.whl", hash = "sha256:51bc9cfef83e0ac84f86bf2b10eaccb27c5a3e66a1212bef676f5bee6ef33ebb"},
|
||||
{file = "SQLAlchemy-2.0.37-cp37-cp37m-win_amd64.whl", hash = "sha256:8e47f1af09444f87c67b4f1bb6231e12ba6d4d9f03050d7fc88df6d075231a49"},
|
||||
{file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6b788f14c5bb91db7f468dcf76f8b64423660a05e57fe277d3f4fad7b9dcb7ce"},
|
||||
{file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521ef85c04c33009166777c77e76c8a676e2d8528dc83a57836b63ca9c69dcd1"},
|
||||
{file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75311559f5c9881a9808eadbeb20ed8d8ba3f7225bef3afed2000c2a9f4d49b9"},
|
||||
{file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cce918ada64c956b62ca2c2af59b125767097ec1dca89650a6221e887521bfd7"},
|
||||
{file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9d087663b7e1feabea8c578d6887d59bb00388158e8bff3a76be11aa3f748ca2"},
|
||||
{file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cf95a60b36997dad99692314c4713f141b61c5b0b4cc5c3426faad570b31ca01"},
|
||||
{file = "SQLAlchemy-2.0.37-cp38-cp38-win32.whl", hash = "sha256:d75ead7dd4d255068ea0f21492ee67937bd7c90964c8f3c2bea83c7b7f81b95f"},
|
||||
{file = "SQLAlchemy-2.0.37-cp38-cp38-win_amd64.whl", hash = "sha256:74bbd1d0a9bacf34266a7907d43260c8d65d31d691bb2356f41b17c2dca5b1d0"},
|
||||
{file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:648ec5acf95ad59255452ef759054f2176849662af4521db6cb245263ae4aa33"},
|
||||
{file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:35bd2df269de082065d4b23ae08502a47255832cc3f17619a5cea92ce478b02b"},
|
||||
{file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f581d365af9373a738c49e0c51e8b18e08d8a6b1b15cc556773bcd8a192fa8b"},
|
||||
{file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82df02816c14f8dc9f4d74aea4cb84a92f4b0620235daa76dde002409a3fbb5a"},
|
||||
{file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94b564e38b344d3e67d2e224f0aec6ba09a77e4582ced41e7bfd0f757d926ec9"},
|
||||
{file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:955a2a765aa1bd81aafa69ffda179d4fe3e2a3ad462a736ae5b6f387f78bfeb8"},
|
||||
{file = "SQLAlchemy-2.0.37-cp39-cp39-win32.whl", hash = "sha256:03f0528c53ca0b67094c4764523c1451ea15959bbf0a8a8a3096900014db0278"},
|
||||
{file = "SQLAlchemy-2.0.37-cp39-cp39-win_amd64.whl", hash = "sha256:4b12885dc85a2ab2b7d00995bac6d967bffa8594123b02ed21e8eb2205a7584b"},
|
||||
{file = "SQLAlchemy-2.0.37-py3-none-any.whl", hash = "sha256:a8998bf9f8658bd3839cbc44ddbe982955641863da0c1efe5b00c1ab4f5c16b1"},
|
||||
{file = "sqlalchemy-2.0.37.tar.gz", hash = "sha256:12b28d99a9c14eaf4055810df1001557176716de0167b91026e648e65229bffb"},
|
||||
{file = "SQLAlchemy-2.0.38-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5e1d9e429028ce04f187a9f522818386c8b076723cdbe9345708384f49ebcec6"},
|
||||
{file = "SQLAlchemy-2.0.38-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b87a90f14c68c925817423b0424381f0e16d80fc9a1a1046ef202ab25b19a444"},
|
||||
{file = "SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:402c2316d95ed90d3d3c25ad0390afa52f4d2c56b348f212aa9c8d072a40eee5"},
|
||||
{file = "SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6493bc0eacdbb2c0f0d260d8988e943fee06089cd239bd7f3d0c45d1657a70e2"},
|
||||
{file = "SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0561832b04c6071bac3aad45b0d3bb6d2c4f46a8409f0a7a9c9fa6673b41bc03"},
|
||||
{file = "SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:49aa2cdd1e88adb1617c672a09bf4ebf2f05c9448c6dbeba096a3aeeb9d4d443"},
|
||||
{file = "SQLAlchemy-2.0.38-cp310-cp310-win32.whl", hash = "sha256:64aa8934200e222f72fcfd82ee71c0130a9c07d5725af6fe6e919017d095b297"},
|
||||
{file = "SQLAlchemy-2.0.38-cp310-cp310-win_amd64.whl", hash = "sha256:c57b8e0841f3fce7b703530ed70c7c36269c6d180ea2e02e36b34cb7288c50c7"},
|
||||
{file = "SQLAlchemy-2.0.38-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf89e0e4a30714b357f5d46b6f20e0099d38b30d45fa68ea48589faf5f12f62d"},
|
||||
{file = "SQLAlchemy-2.0.38-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8455aa60da49cb112df62b4721bd8ad3654a3a02b9452c783e651637a1f21fa2"},
|
||||
{file = "SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53c0d6a859b2db58332e0e6a921582a02c1677cc93d4cbb36fdf49709b327b2"},
|
||||
{file = "SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c4817dff8cef5697f5afe5fec6bc1783994d55a68391be24cb7d80d2dbc3a6"},
|
||||
{file = "SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9cea5b756173bb86e2235f2f871b406a9b9d722417ae31e5391ccaef5348f2c"},
|
||||
{file = "SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40e9cdbd18c1f84631312b64993f7d755d85a3930252f6276a77432a2b25a2f3"},
|
||||
{file = "SQLAlchemy-2.0.38-cp311-cp311-win32.whl", hash = "sha256:cb39ed598aaf102251483f3e4675c5dd6b289c8142210ef76ba24aae0a8f8aba"},
|
||||
{file = "SQLAlchemy-2.0.38-cp311-cp311-win_amd64.whl", hash = "sha256:f9d57f1b3061b3e21476b0ad5f0397b112b94ace21d1f439f2db472e568178ae"},
|
||||
{file = "SQLAlchemy-2.0.38-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12d5b06a1f3aeccf295a5843c86835033797fea292c60e72b07bcb5d820e6dd3"},
|
||||
{file = "SQLAlchemy-2.0.38-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e036549ad14f2b414c725349cce0772ea34a7ab008e9cd67f9084e4f371d1f32"},
|
||||
{file = "SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee3bee874cb1fadee2ff2b79fc9fc808aa638670f28b2145074538d4a6a5028e"},
|
||||
{file = "SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e185ea07a99ce8b8edfc788c586c538c4b1351007e614ceb708fd01b095ef33e"},
|
||||
{file = "SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b79ee64d01d05a5476d5cceb3c27b5535e6bb84ee0f872ba60d9a8cd4d0e6579"},
|
||||
{file = "SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afd776cf1ebfc7f9aa42a09cf19feadb40a26366802d86c1fba080d8e5e74bdd"},
|
||||
{file = "SQLAlchemy-2.0.38-cp312-cp312-win32.whl", hash = "sha256:a5645cd45f56895cfe3ca3459aed9ff2d3f9aaa29ff7edf557fa7a23515a3725"},
|
||||
{file = "SQLAlchemy-2.0.38-cp312-cp312-win_amd64.whl", hash = "sha256:1052723e6cd95312f6a6eff9a279fd41bbae67633415373fdac3c430eca3425d"},
|
||||
{file = "SQLAlchemy-2.0.38-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ecef029b69843b82048c5b347d8e6049356aa24ed644006c9a9d7098c3bd3bfd"},
|
||||
{file = "SQLAlchemy-2.0.38-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c8bcad7fc12f0cc5896d8e10fdf703c45bd487294a986903fe032c72201596b"},
|
||||
{file = "SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0ef3f98175d77180ffdc623d38e9f1736e8d86b6ba70bff182a7e68bed7727"},
|
||||
{file = "SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ac78898c50e2574e9f938d2e5caa8fe187d7a5b69b65faa1ea4648925b096"},
|
||||
{file = "SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9eb4fa13c8c7a2404b6a8e3772c17a55b1ba18bc711e25e4d6c0c9f5f541b02a"},
|
||||
{file = "SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5dba1cdb8f319084f5b00d41207b2079822aa8d6a4667c0f369fce85e34b0c86"},
|
||||
{file = "SQLAlchemy-2.0.38-cp313-cp313-win32.whl", hash = "sha256:eae27ad7580529a427cfdd52c87abb2dfb15ce2b7a3e0fc29fbb63e2ed6f8120"},
|
||||
{file = "SQLAlchemy-2.0.38-cp313-cp313-win_amd64.whl", hash = "sha256:b335a7c958bc945e10c522c069cd6e5804f4ff20f9a744dd38e748eb602cbbda"},
|
||||
{file = "SQLAlchemy-2.0.38-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:40310db77a55512a18827488e592965d3dec6a3f1e3d8af3f8243134029daca3"},
|
||||
{file = "SQLAlchemy-2.0.38-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d3043375dd5bbcb2282894cbb12e6c559654c67b5fffb462fda815a55bf93f7"},
|
||||
{file = "SQLAlchemy-2.0.38-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70065dfabf023b155a9c2a18f573e47e6ca709b9e8619b2e04c54d5bcf193178"},
|
||||
{file = "SQLAlchemy-2.0.38-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:c058b84c3b24812c859300f3b5abf300daa34df20d4d4f42e9652a4d1c48c8a4"},
|
||||
{file = "SQLAlchemy-2.0.38-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0398361acebb42975deb747a824b5188817d32b5c8f8aba767d51ad0cc7bb08d"},
|
||||
{file = "SQLAlchemy-2.0.38-cp37-cp37m-win32.whl", hash = "sha256:a2bc4e49e8329f3283d99840c136ff2cd1a29e49b5624a46a290f04dff48e079"},
|
||||
{file = "SQLAlchemy-2.0.38-cp37-cp37m-win_amd64.whl", hash = "sha256:9cd136184dd5f58892f24001cdce986f5d7e96059d004118d5410671579834a4"},
|
||||
{file = "SQLAlchemy-2.0.38-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:665255e7aae5f38237b3a6eae49d2358d83a59f39ac21036413fab5d1e810578"},
|
||||
{file = "SQLAlchemy-2.0.38-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:92f99f2623ff16bd4aaf786ccde759c1f676d39c7bf2855eb0b540e1ac4530c8"},
|
||||
{file = "SQLAlchemy-2.0.38-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa498d1392216fae47eaf10c593e06c34476ced9549657fca713d0d1ba5f7248"},
|
||||
{file = "SQLAlchemy-2.0.38-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9afbc3909d0274d6ac8ec891e30210563b2c8bdd52ebbda14146354e7a69373"},
|
||||
{file = "SQLAlchemy-2.0.38-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:57dd41ba32430cbcc812041d4de8d2ca4651aeefad2626921ae2a23deb8cd6ff"},
|
||||
{file = "SQLAlchemy-2.0.38-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3e35d5565b35b66905b79ca4ae85840a8d40d31e0b3e2990f2e7692071b179ca"},
|
||||
{file = "SQLAlchemy-2.0.38-cp38-cp38-win32.whl", hash = "sha256:f0d3de936b192980209d7b5149e3c98977c3810d401482d05fb6d668d53c1c63"},
|
||||
{file = "SQLAlchemy-2.0.38-cp38-cp38-win_amd64.whl", hash = "sha256:3868acb639c136d98107c9096303d2d8e5da2880f7706f9f8c06a7f961961149"},
|
||||
{file = "SQLAlchemy-2.0.38-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07258341402a718f166618470cde0c34e4cec85a39767dce4e24f61ba5e667ea"},
|
||||
{file = "SQLAlchemy-2.0.38-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a826f21848632add58bef4f755a33d45105d25656a0c849f2dc2df1c71f6f50"},
|
||||
{file = "SQLAlchemy-2.0.38-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:386b7d136919bb66ced64d2228b92d66140de5fefb3c7df6bd79069a269a7b06"},
|
||||
{file = "SQLAlchemy-2.0.38-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f2951dc4b4f990a4b394d6b382accb33141d4d3bd3ef4e2b27287135d6bdd68"},
|
||||
{file = "SQLAlchemy-2.0.38-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8bf312ed8ac096d674c6aa9131b249093c1b37c35db6a967daa4c84746bc1bc9"},
|
||||
{file = "SQLAlchemy-2.0.38-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6db316d6e340f862ec059dc12e395d71f39746a20503b124edc255973977b728"},
|
||||
{file = "SQLAlchemy-2.0.38-cp39-cp39-win32.whl", hash = "sha256:c09a6ea87658695e527104cf857c70f79f14e9484605e205217aae0ec27b45fc"},
|
||||
{file = "SQLAlchemy-2.0.38-cp39-cp39-win_amd64.whl", hash = "sha256:12f5c9ed53334c3ce719155424dc5407aaa4f6cadeb09c5b627e06abb93933a1"},
|
||||
{file = "SQLAlchemy-2.0.38-py3-none-any.whl", hash = "sha256:63178c675d4c80def39f1febd625a6333f44c0ba269edd8a468b156394b27753"},
|
||||
{file = "sqlalchemy-2.0.38.tar.gz", hash = "sha256:e5a4d82bdb4bf1ac1285a68eab02d253ab73355d9f0fe725a97e1e0fa689decb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -2999,15 +3004,15 @@ standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)",
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.29.1"
|
||||
version = "20.29.2"
|
||||
description = "Virtual Python Environment builder"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
||||
files = [
|
||||
{file = "virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779"},
|
||||
{file = "virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35"},
|
||||
{file = "virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a"},
|
||||
{file = "virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3183,4 +3188,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.10, <4.0"
|
||||
content-hash = "3b7e6e6e872c68f951f191d85a7d76fe1dd86caf32e2143a53a3152a3686fc7f"
|
||||
content-hash = "7ae644e1c5b910f4fd0d8ab0b530818077a96e5d329b2be1269e967c6b0b3d25"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "reflex"
|
||||
version = "0.7.0dev1"
|
||||
version = "0.7.1dev1"
|
||||
description = "Web apps in pure Python."
|
||||
license = "Apache-2.0"
|
||||
authors = [
|
||||
@ -61,7 +61,7 @@ dill = ">=0.3.8"
|
||||
toml = ">=0.10.2,<1.0"
|
||||
pytest-asyncio = ">=0.24.0"
|
||||
pytest-cov = ">=4.0.0,<7.0"
|
||||
ruff = "0.9.3"
|
||||
ruff = "0.9.6"
|
||||
pandas = ">=2.1.1,<3.0"
|
||||
pillow = ">=10.0.0,<12.0"
|
||||
plotly = ">=5.13.0,<6.0"
|
||||
|
299
reflex/app.py
299
reflex/app.py
@ -11,17 +11,17 @@ import functools
|
||||
import inspect
|
||||
import io
|
||||
import json
|
||||
import multiprocessing
|
||||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from timeit import default_timer as timer
|
||||
from types import SimpleNamespace
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
AsyncIterator,
|
||||
BinaryIO,
|
||||
Callable,
|
||||
Coroutine,
|
||||
Dict,
|
||||
@ -35,12 +35,15 @@ from typing import (
|
||||
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.responses import JSONResponse, StreamingResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
|
||||
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.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.upload import Upload, get_upload_dir
|
||||
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 (
|
||||
_EVENT_FIELDS,
|
||||
Event,
|
||||
@ -100,7 +103,15 @@ from reflex.state import (
|
||||
all_base_state_classes,
|
||||
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.imports import ImportVar
|
||||
|
||||
@ -158,11 +169,11 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
|
||||
return window_alert("\n".join(error_message))
|
||||
|
||||
|
||||
def default_overlay_component() -> Component:
|
||||
"""Default overlay_component attribute for App.
|
||||
def extra_overlay_function() -> Optional[Component]:
|
||||
"""Extra overlay function to add to the overlay component.
|
||||
|
||||
Returns:
|
||||
The default overlay_component, which is a connection_modal.
|
||||
The extra overlay function.
|
||||
"""
|
||||
config = get_config()
|
||||
|
||||
@ -172,7 +183,8 @@ def default_overlay_component() -> Component:
|
||||
module, _, function_name = extra_config.rpartition(".")
|
||||
try:
|
||||
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:
|
||||
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}"
|
||||
)
|
||||
|
||||
return Fragment.create(
|
||||
connection_pulser(),
|
||||
connection_toaster(),
|
||||
*([config_overlay] if config_overlay else []),
|
||||
*([backend_disabled()] if config.is_reflex_cloud else []),
|
||||
*codespaces.codespaces_auto_redirect(),
|
||||
)
|
||||
return config_overlay
|
||||
|
||||
|
||||
def default_overlay_component() -> Component:
|
||||
"""Default overlay_component attribute for App.
|
||||
|
||||
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:
|
||||
@ -210,6 +236,53 @@ class OverlayFragment(Fragment):
|
||||
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(
|
||||
frozen=True,
|
||||
)
|
||||
@ -260,11 +333,26 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
# A component that is present on every page (defaults to the Connection Error banner).
|
||||
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: 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.
|
||||
head_components: List[Component] = dataclasses.field(default_factory=list)
|
||||
@ -322,6 +410,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
[Exception], Union[EventSpec, List[EventSpec], None]
|
||||
] = default_backend_exception_handler
|
||||
|
||||
# Put the toast provider in the app wrap.
|
||||
bundle_toaster: bool = True
|
||||
|
||||
@property
|
||||
def api(self) -> FastAPI | None:
|
||||
"""Get the backend api.
|
||||
@ -560,7 +651,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
Returns:
|
||||
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(
|
||||
self,
|
||||
@ -888,25 +981,6 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
for k, component in self._pages.items():
|
||||
self._pages[k] = self._add_overlay_to_component(component)
|
||||
|
||||
def _add_error_boundary_to_component(self, component: Component) -> Component:
|
||||
if self.error_boundary is None:
|
||||
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):
|
||||
"""Add the sticky badge to the app."""
|
||||
for k, component in self._pages.items():
|
||||
@ -1021,9 +1095,14 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
should_compile = self._should_compile()
|
||||
|
||||
if not should_compile:
|
||||
for route in self._unevaluated_pages:
|
||||
console.debug(f"Evaluating page: {route}")
|
||||
self._compile_page(route, save_page=should_compile)
|
||||
if self.bundle_toaster:
|
||||
from reflex.components.sonner.toast import Toaster
|
||||
|
||||
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)
|
||||
self._add_optional_endpoints()
|
||||
@ -1049,24 +1128,51 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
+ adhoc_steps_without_executor,
|
||||
)
|
||||
|
||||
for route in self._unevaluated_pages:
|
||||
console.debug(f"Evaluating page: {route}")
|
||||
self._compile_page(route, save_page=should_compile)
|
||||
progress.advance(task)
|
||||
if self.bundle_toaster:
|
||||
from reflex.components.component import memo
|
||||
from reflex.components.sonner.toast import toast
|
||||
|
||||
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)
|
||||
self._add_optional_endpoints()
|
||||
|
||||
self._validate_var_dependencies()
|
||||
self._setup_overlay_component()
|
||||
self._setup_error_boundary()
|
||||
if config.show_built_with_reflex:
|
||||
if is_prod_mode() and config.show_built_with_reflex:
|
||||
self._setup_sticky_badge()
|
||||
|
||||
progress.advance(task)
|
||||
|
||||
# Store the compile results.
|
||||
compile_results = []
|
||||
compile_results: list[tuple[str, str]] = []
|
||||
|
||||
progress.advance(task)
|
||||
|
||||
@ -1086,14 +1192,32 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
# Add the custom components from the page to the set.
|
||||
custom_components |= component._get_all_custom_components()
|
||||
|
||||
# Perform auto-memoization of stateful components.
|
||||
(
|
||||
stateful_components_path,
|
||||
stateful_components_code,
|
||||
page_components,
|
||||
) = compiler.compile_stateful_components(self._pages.values())
|
||||
# Add the app wraps to the app.
|
||||
for key, app_wrap in self.app_wraps.items():
|
||||
component = app_wrap(self._state is not None)
|
||||
if component is not None:
|
||||
app_wrappers[key] = component
|
||||
|
||||
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.
|
||||
if code_uses_state_contexts(stateful_components_code) and self._state is None:
|
||||
@ -1116,33 +1240,30 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
progress.advance(task)
|
||||
|
||||
# Use a forking process pool, if possible. Much faster, especially for large sites.
|
||||
# Fallback to ThreadPoolExecutor as something that will always work.
|
||||
executor = None
|
||||
if (
|
||||
platform.system() in ("Linux", "Darwin")
|
||||
and (number_of_processes := environment.REFLEX_COMPILE_PROCESSES.get())
|
||||
is not None
|
||||
):
|
||||
executor = concurrent.futures.ProcessPoolExecutor(
|
||||
max_workers=number_of_processes or None,
|
||||
mp_context=multiprocessing.get_context("fork"),
|
||||
)
|
||||
else:
|
||||
executor = concurrent.futures.ThreadPoolExecutor(
|
||||
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
|
||||
)
|
||||
# Copy the assets.
|
||||
assets_src = Path.cwd() / constants.Dirs.APP_ASSETS
|
||||
if assets_src.is_dir():
|
||||
with console.timing("Copy assets"):
|
||||
path_ops.update_directory_tree(
|
||||
src=assets_src,
|
||||
dest=(
|
||||
Path.cwd() / prerequisites.get_web_dir() / constants.Dirs.PUBLIC
|
||||
),
|
||||
)
|
||||
|
||||
executor = ExecutorType.get_executor_from_environment()
|
||||
|
||||
for route, component in zip(self._pages, page_components, strict=True):
|
||||
ExecutorSafeFunctions.COMPONENTS[route] = component
|
||||
|
||||
ExecutorSafeFunctions.STATE = self._state
|
||||
|
||||
with executor:
|
||||
result_futures = []
|
||||
with console.timing("Compile to Javascript"), executor as executor:
|
||||
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.add_done_callback(lambda _: progress.advance(task))
|
||||
result_futures.append(f)
|
||||
|
||||
# Compile the pre-compiled pages.
|
||||
@ -1168,9 +1289,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
_submit_work(compiler.remove_tailwind_from_postcss)
|
||||
|
||||
# Wait for all compilation tasks to complete.
|
||||
for future in concurrent.futures.as_completed(result_futures):
|
||||
compile_results.append(future.result())
|
||||
progress.advance(task)
|
||||
compile_results.extend(
|
||||
future.result()
|
||||
for future in concurrent.futures.as_completed(result_futures)
|
||||
)
|
||||
|
||||
app_root = self._app_root(app_wrappers=app_wrappers)
|
||||
|
||||
@ -1195,17 +1317,20 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
progress.advance(task)
|
||||
|
||||
# Compile custom components.
|
||||
*custom_components_result, custom_components_imports = (
|
||||
compiler.compile_components(custom_components)
|
||||
)
|
||||
compile_results.append(custom_components_result)
|
||||
(
|
||||
custom_components_output,
|
||||
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)
|
||||
|
||||
progress.advance(task)
|
||||
progress.stop()
|
||||
|
||||
# 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
|
||||
transpile_packages = [
|
||||
@ -1231,8 +1356,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
# Remove pages that are no longer in the app.
|
||||
p.unlink()
|
||||
|
||||
for output_path, code in compile_results:
|
||||
compiler_utils.write_page(output_path, code)
|
||||
with console.timing("Write to Disk"):
|
||||
for output_path, code in compile_results:
|
||||
compiler_utils.write_page(output_path, code)
|
||||
|
||||
# Pickle dynamic states
|
||||
if self._state is not None:
|
||||
@ -1549,7 +1675,7 @@ def upload(app: App):
|
||||
The upload function.
|
||||
"""
|
||||
|
||||
async def upload_file(request: Request, files: List[UploadFile]):
|
||||
async def upload_file(request: Request, files: List[FastAPIUploadFile]):
|
||||
"""Upload a file.
|
||||
|
||||
Args:
|
||||
@ -1625,7 +1751,8 @@ def upload(app: App):
|
||||
file_copies.append(
|
||||
UploadFile(
|
||||
file=content_copy,
|
||||
filename=file.filename,
|
||||
path=Path(file.filename.lstrip("/")) if file.filename else None,
|
||||
_deprecated_filename=file.filename,
|
||||
size=file.size,
|
||||
headers=file.headers,
|
||||
)
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
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.compiler import templates, utils
|
||||
@ -78,6 +78,7 @@ def _compile_app(app_root: Component) -> str:
|
||||
hooks=app_root._get_all_hooks(),
|
||||
window_libraries=window_libraries,
|
||||
render=app_root.render(),
|
||||
dynamic_imports=app_root._get_all_dynamic_imports(),
|
||||
)
|
||||
|
||||
|
||||
@ -507,7 +508,7 @@ def compile_tailwind(
|
||||
The compiled Tailwind config.
|
||||
"""
|
||||
# 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.
|
||||
code = _compile_tailwind(config)
|
||||
@ -544,7 +545,47 @@ def purge_web_pages_dir():
|
||||
|
||||
|
||||
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(
|
||||
@ -567,12 +608,7 @@ def compile_unevaluated_page(
|
||||
The compiled component and whether state should be enabled.
|
||||
"""
|
||||
# Generate the component if it is a callable.
|
||||
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 = into_component(page.component)
|
||||
|
||||
component._add_style_recursive(style or {}, theme)
|
||||
|
||||
@ -677,10 +713,8 @@ class ExecutorSafeFunctions:
|
||||
The route, compiled component, and compiled 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)
|
||||
|
||||
@classmethod
|
||||
|
@ -48,11 +48,10 @@ class ReflexJinjaEnvironment(Environment):
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Set default environment."""
|
||||
extensions = ["jinja2.ext.debug"]
|
||||
super().__init__(
|
||||
extensions=extensions,
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
auto_reload=False,
|
||||
)
|
||||
self.filters["json_dumps"] = json_dumps
|
||||
self.filters["react_setter"] = lambda state: f"set{state.capitalize()}"
|
||||
|
@ -119,24 +119,34 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]:
|
||||
validate_imports(collapsed_import_dict)
|
||||
import_dicts = []
|
||||
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
|
||||
if not any(f.render for f in fields):
|
||||
continue
|
||||
|
||||
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
|
||||
lib_paths: dict[str, list[ImportVar]] = {}
|
||||
|
||||
# remove the version before rendering the package imports
|
||||
lib = format.format_library_name(lib)
|
||||
for field in fields:
|
||||
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
|
||||
|
||||
|
||||
@ -513,6 +523,8 @@ def write_page(path: str | Path, code: str):
|
||||
"""
|
||||
path = Path(path)
|
||||
path_ops.mkdir(path.parent)
|
||||
if path.exists() and path.read_text(encoding="utf-8") == code:
|
||||
return
|
||||
path.write_text(code, encoding="utf-8")
|
||||
|
||||
|
||||
|
@ -7,9 +7,44 @@ from typing import Any, Iterator
|
||||
from reflex.components.component import Component, LiteralComponentVar
|
||||
from reflex.components.tags import Tag
|
||||
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.vars import BooleanVar, ObjectVar, Var
|
||||
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):
|
||||
@ -28,9 +63,14 @@ class Bare(Component):
|
||||
The component.
|
||||
"""
|
||||
if isinstance(contents, Var):
|
||||
if isinstance(contents, LiteralStringVar):
|
||||
validate_str(contents._var_value)
|
||||
return cls(contents=contents)
|
||||
else:
|
||||
if isinstance(contents, str):
|
||||
validate_str(contents)
|
||||
contents = str(contents) if contents is not None else ""
|
||||
|
||||
return cls(contents=contents)
|
||||
|
||||
def _get_all_hooks_internal(self) -> dict[str, VarData | None]:
|
||||
|
@ -23,6 +23,8 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
import reflex.state
|
||||
from reflex.base import Base
|
||||
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.utils import format, imports, types
|
||||
from reflex.utils.imports import (
|
||||
ImmutableParsedImportDict,
|
||||
ImportDict,
|
||||
ImportVar,
|
||||
ParsedImportDict,
|
||||
parse_imports,
|
||||
)
|
||||
from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports
|
||||
from reflex.vars import VarData
|
||||
from reflex.vars.base import (
|
||||
CachedVarOperation,
|
||||
@ -179,6 +175,7 @@ ComponentStyle = Dict[
|
||||
Union[str, Type[BaseComponent], Callable, ComponentNamespace], Any
|
||||
]
|
||||
ComponentChild = Union[types.PrimitiveType, Var, BaseComponent]
|
||||
ComponentChildTypes = (*types.PrimitiveTypes, Var, BaseComponent)
|
||||
|
||||
|
||||
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:
|
||||
Whether the object satisfies the type hint.
|
||||
"""
|
||||
if isinstance(obj, LiteralVar):
|
||||
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)
|
||||
return types._isinstance(obj, type_hint, nested=1)
|
||||
|
||||
|
||||
class Component(BaseComponent, ABC):
|
||||
@ -688,7 +681,7 @@ class Component(BaseComponent, ABC):
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def create(cls, *children, **props) -> Component:
|
||||
def create(cls, *children, **props) -> Self:
|
||||
"""Create the component.
|
||||
|
||||
Args:
|
||||
@ -712,8 +705,8 @@ class Component(BaseComponent, ABC):
|
||||
validate_children(child)
|
||||
|
||||
# Make sure the child is a valid type.
|
||||
if isinstance(child, dict) or not types._isinstance(
|
||||
child, ComponentChild
|
||||
if isinstance(child, dict) or not isinstance(
|
||||
child, ComponentChildTypes
|
||||
):
|
||||
raise ChildrenTypeError(component=cls.__name__, child=child)
|
||||
|
||||
@ -1209,7 +1202,7 @@ class Component(BaseComponent, ABC):
|
||||
Returns:
|
||||
True if the dependency should be transpiled.
|
||||
"""
|
||||
return (
|
||||
return bool(self.transpile_packages) and (
|
||||
dep 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 {}
|
||||
|
||||
# Collect imports from Vars used directly by this component.
|
||||
var_datas = [var._get_all_var_data() for var in self._get_vars()]
|
||||
var_imports: List[ImmutableParsedImportDict] = [
|
||||
var_data.imports for var_data in var_datas if var_data is not None
|
||||
var_imports = [
|
||||
var_data.imports
|
||||
for var in self._get_vars()
|
||||
if (var_data := var._get_all_var_data()) is not None
|
||||
]
|
||||
|
||||
added_import_dicts: list[ParsedImportDict] = []
|
||||
@ -1771,9 +1765,7 @@ class CustomComponent(Component):
|
||||
return [
|
||||
Var(
|
||||
_js_expr=name,
|
||||
_var_type=(
|
||||
prop._var_type if types._isinstance(prop, Var) else type(prop)
|
||||
),
|
||||
_var_type=(prop._var_type if isinstance(prop, Var) else type(prop)),
|
||||
).guess_type()
|
||||
for name, prop in self.props.items()
|
||||
]
|
||||
@ -1795,9 +1787,6 @@ class CustomComponent(Component):
|
||||
include_children=include_children, ignore_ids=ignore_ids
|
||||
)
|
||||
yield from filter(lambda prop: isinstance(prop, Var), self.props.values())
|
||||
yield from self.get_component(self)._get_vars(
|
||||
include_children=include_children, ignore_ids=ignore_ids
|
||||
)
|
||||
|
||||
@lru_cache(maxsize=None) # noqa: B019
|
||||
def get_component(self) -> Component:
|
||||
|
@ -54,9 +54,10 @@ class Foreach(Component):
|
||||
TypeError: If the render function is a ComponentState.
|
||||
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:
|
||||
raise ForeachVarError(
|
||||
f"Could not foreach over var `{iterable!s}` of type Any. "
|
||||
@ -75,6 +76,15 @@ class Foreach(Component):
|
||||
if isinstance(iterable, ObjectVar):
|
||||
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(
|
||||
iterable=iterable,
|
||||
render_fn=render_fn,
|
||||
|
@ -178,9 +178,9 @@ class Match(MemoizationLeaf):
|
||||
first_case_return = match_cases[0][-1]
|
||||
return_type = type(first_case_return)
|
||||
|
||||
if types._isinstance(first_case_return, BaseComponent):
|
||||
if isinstance(first_case_return, BaseComponent):
|
||||
return_type = BaseComponent
|
||||
elif types._isinstance(first_case_return, Var):
|
||||
elif isinstance(first_case_return, Var):
|
||||
return_type = Var
|
||||
|
||||
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.
|
||||
if any(
|
||||
case for case in match_cases if not types._isinstance(case[-1], Var)
|
||||
) or not types._isinstance(default, Var):
|
||||
case for case in match_cases if not isinstance(case[-1], Var)
|
||||
) or not isinstance(default, Var):
|
||||
raise ValueError("Return types of match cases should be Vars.")
|
||||
|
||||
return Var(
|
||||
|
@ -2,14 +2,12 @@
|
||||
|
||||
from reflex.components.component import ComponentNamespace
|
||||
from reflex.components.core.colors import color
|
||||
from reflex.components.core.cond import color_mode_cond, cond
|
||||
from reflex.components.core.responsive import tablet_and_desktop
|
||||
from reflex.components.core.cond import color_mode_cond
|
||||
from reflex.components.core.responsive import desktop_only
|
||||
from reflex.components.el.elements.inline import A
|
||||
from reflex.components.el.elements.media import Path, Rect, Svg
|
||||
from reflex.components.radix.themes.typography.text import Text
|
||||
from reflex.experimental.client_state import ClientStateVar
|
||||
from reflex.style import Style
|
||||
from reflex.vars.base import Var, VarData
|
||||
|
||||
|
||||
class StickyLogo(Svg):
|
||||
@ -87,7 +85,7 @@ class StickyBadge(A):
|
||||
"""
|
||||
return super().create(
|
||||
StickyLogo.create(),
|
||||
tablet_and_desktop(StickyLabel.create()),
|
||||
desktop_only(StickyLabel.create()),
|
||||
href="https://reflex.dev",
|
||||
target="_blank",
|
||||
width="auto",
|
||||
@ -102,36 +100,12 @@ class StickyBadge(A):
|
||||
Returns:
|
||||
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(
|
||||
{
|
||||
"position": "fixed",
|
||||
"bottom": "1rem",
|
||||
"right": "1rem",
|
||||
# Do not show the badge on localhost.
|
||||
"display": cond(is_localhost, "none", "flex"),
|
||||
"display": "flex",
|
||||
"flex-direction": "row",
|
||||
"gap": "0.375rem",
|
||||
"align-items": "center",
|
||||
|
@ -29,7 +29,7 @@ from reflex.event import (
|
||||
from reflex.utils import format
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.vars import VarData
|
||||
from reflex.vars.base import CallableVar, Var, get_unique_variable_name
|
||||
from reflex.vars.base import Var, get_unique_variable_name
|
||||
from reflex.vars.sequence import LiteralStringVar
|
||||
|
||||
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:
|
||||
"""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:
|
||||
"""Get the list of selected files.
|
||||
|
||||
|
@ -13,14 +13,12 @@ from reflex.event import CallableEventSpec, EventSpec, EventType
|
||||
from reflex.style import Style
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.vars import VarData
|
||||
from reflex.vars.base import CallableVar, Var
|
||||
from reflex.vars.base import Var
|
||||
|
||||
DEFAULT_UPLOAD_ID: str
|
||||
upload_files_context_var_data: VarData
|
||||
|
||||
@CallableVar
|
||||
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var: ...
|
||||
@CallableVar
|
||||
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var: ...
|
||||
@CallableEventSpec
|
||||
def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ...
|
||||
|
@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import typing
|
||||
from typing import ClassVar, Dict, Literal, Optional, Union
|
||||
|
||||
from reflex.components.component import Component, ComponentNamespace
|
||||
@ -503,7 +504,7 @@ class CodeBlock(Component, MarkdownComponentMap):
|
||||
return ["can_copy", "copy_button"]
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
@ -514,21 +515,46 @@ class CodeBlock(Component, MarkdownComponentMap):
|
||||
Returns:
|
||||
The hook to register the language.
|
||||
"""
|
||||
return f"""
|
||||
if ({language_var!s}) {{
|
||||
(async () => {{
|
||||
try {{
|
||||
language_in_there = Var.create(typing.get_args(LiteralCodeLanguage)).contains(
|
||||
language_var
|
||||
)
|
||||
async_load = f"""
|
||||
(async () => {{
|
||||
try {{
|
||||
const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{language_var!s}}}`);
|
||||
SyntaxHighlighter.registerLanguage({language_var!s}, module.default);
|
||||
}} catch (error) {{
|
||||
console.error(`Error importing language module for ${{{language_var!s}}}:`, error);
|
||||
}}
|
||||
}})();
|
||||
}} catch (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
|
||||
def get_component_map_custom_code(cls) -> str:
|
||||
def get_component_map_custom_code(cls) -> Var:
|
||||
"""Get the custom code for the component.
|
||||
|
||||
Returns:
|
||||
|
@ -984,7 +984,7 @@ class CodeBlock(Component, MarkdownComponentMap):
|
||||
|
||||
def add_style(self): ...
|
||||
@classmethod
|
||||
def get_component_map_custom_code(cls) -> str: ...
|
||||
def get_component_map_custom_code(cls) -> Var: ...
|
||||
def add_hooks(self) -> list[str | Var]: ...
|
||||
|
||||
class CodeblockNamespace(ComponentNamespace):
|
||||
|
@ -4,7 +4,7 @@ from reflex.components.component import Component
|
||||
from reflex.utils import format
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.vars.base import LiteralVar, Var
|
||||
from reflex.vars.sequence import LiteralStringVar
|
||||
from reflex.vars.sequence import LiteralStringVar, StringVar
|
||||
|
||||
|
||||
class LucideIconComponent(Component):
|
||||
@ -40,7 +40,12 @@ class Icon(LucideIconComponent):
|
||||
The created component.
|
||||
"""
|
||||
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]
|
||||
else:
|
||||
raise AttributeError(
|
||||
@ -49,22 +54,33 @@ class Icon(LucideIconComponent):
|
||||
if "tag" not in props:
|
||||
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, LiteralStringVar):
|
||||
tag = tag._var_value
|
||||
else:
|
||||
raise TypeError(f"Icon name must be a string, got {type(tag)}")
|
||||
elif isinstance(tag, Var):
|
||||
return DynamicIcon.create(name=tag, **props)
|
||||
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 (
|
||||
not isinstance(tag, str)
|
||||
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(
|
||||
f"Invalid icon tag: {tag}. Please use one of the following: {', '.join(LUCIDE_ICON_LIST[0:25])}, ..."
|
||||
"\nSee full list at https://lucide.dev/icons."
|
||||
f"Invalid icon tag: {tag}. Please use one of the following: {', '.join(icons_sorted[0:25])}, ..."
|
||||
"\nSee full list at https://reflex.dev/docs/library/data-display/icon/#icons-list."
|
||||
)
|
||||
|
||||
if tag in LUCIDE_ICON_MAPPING_OVERRIDE:
|
||||
|
@ -6,13 +6,12 @@ import dataclasses
|
||||
import textwrap
|
||||
from functools import lru_cache
|
||||
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.tags.tag import Tag
|
||||
from reflex.utils import types
|
||||
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.number import ternary_operation
|
||||
|
||||
@ -83,13 +82,13 @@ class MarkdownComponentMap:
|
||||
_explicit_return: bool = dataclasses.field(default=False)
|
||||
|
||||
@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.
|
||||
|
||||
Returns:
|
||||
The custom code for the component map.
|
||||
"""
|
||||
return ""
|
||||
return Var("")
|
||||
|
||||
@classmethod
|
||||
def create_map_fn_var(
|
||||
@ -97,6 +96,7 @@ class MarkdownComponentMap:
|
||||
fn_body: Var | None = None,
|
||||
fn_args: Sequence[str] | None = None,
|
||||
explicit_return: bool | None = None,
|
||||
var_data: VarData | None = None,
|
||||
) -> Var:
|
||||
"""Create a function Var for the component map.
|
||||
|
||||
@ -104,6 +104,7 @@ class MarkdownComponentMap:
|
||||
fn_body: The formatted component as a string.
|
||||
fn_args: The function arguments.
|
||||
explicit_return: Whether to use explicit return syntax.
|
||||
var_data: The var data for the function.
|
||||
|
||||
Returns:
|
||||
The function Var for the component map.
|
||||
@ -116,6 +117,7 @@ class MarkdownComponentMap:
|
||||
args_names=(DestructuredArg(fields=tuple(fn_args)),),
|
||||
return_expr=fn_body,
|
||||
explicit_return=explicit_return,
|
||||
_var_data=var_data,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@ -166,7 +168,7 @@ class Markdown(Component):
|
||||
Returns:
|
||||
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(
|
||||
"Markdown component must have exactly one child containing the markdown source."
|
||||
)
|
||||
@ -239,6 +241,15 @@ class Markdown(Component):
|
||||
component(_MOCK_ARG)._get_all_imports()
|
||||
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:
|
||||
@ -278,12 +289,20 @@ class Markdown(Component):
|
||||
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.
|
||||
formatted_code = f"""
|
||||
const match = (className || '').match(/language-(?<lang>.*)/);
|
||||
const {_LANGUAGE!s} = match ? match[1] : '';
|
||||
let {_LANGUAGE!s} = match ? match[1] : '';
|
||||
{codeblock_custom_code};
|
||||
return inline ? (
|
||||
{self.format_component("code")}
|
||||
@ -302,6 +321,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
||||
),
|
||||
fn_body=Var(_js_expr=formatted_code),
|
||||
explicit_return=True,
|
||||
var_data=var_data,
|
||||
)
|
||||
|
||||
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(
|
||||
self, component: BaseComponent
|
||||
) -> list[str]:
|
||||
) -> list[str | Var]:
|
||||
"""Recursively get markdown custom code from children components.
|
||||
|
||||
Args:
|
||||
@ -390,7 +410,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
||||
Returns:
|
||||
A list of markdown custom code strings.
|
||||
"""
|
||||
custom_code_list = []
|
||||
custom_code_list: list[str | Var] = []
|
||||
if isinstance(component, MarkdownComponentMap):
|
||||
custom_code_list.append(component.get_component_map_custom_code())
|
||||
|
||||
|
@ -11,7 +11,7 @@ from reflex.components.component import Component
|
||||
from reflex.event import EventType
|
||||
from reflex.style import Style
|
||||
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)
|
||||
_PROPS = Var(_js_expr="...props")
|
||||
@ -32,13 +32,14 @@ def get_base_component_map() -> dict[str, Callable]: ...
|
||||
@dataclasses.dataclass()
|
||||
class MarkdownComponentMap:
|
||||
@classmethod
|
||||
def get_component_map_custom_code(cls) -> str: ...
|
||||
def get_component_map_custom_code(cls) -> Var: ...
|
||||
@classmethod
|
||||
def create_map_fn_var(
|
||||
cls,
|
||||
fn_body: Var | None = None,
|
||||
fn_args: Sequence[str] | None = None,
|
||||
explicit_return: bool | None = None,
|
||||
var_data: VarData | None = None,
|
||||
) -> Var: ...
|
||||
@classmethod
|
||||
def get_fn_args(cls) -> Sequence[str]: ...
|
||||
|
@ -1,5 +1,32 @@
|
||||
"""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()
|
||||
|
@ -10,6 +10,7 @@ from reflex.components.component import Component, NoSSRComponent
|
||||
from reflex.components.core.cond import color_mode_cond
|
||||
from reflex.event import EventHandler, no_args_event_spec
|
||||
from reflex.utils import console
|
||||
from reflex.utils.imports import ImportDict, ImportVar
|
||||
from reflex.vars.base import LiteralVar, Var
|
||||
|
||||
try:
|
||||
@ -278,3 +279,237 @@ const extractPoints = (points) => {
|
||||
# Spread the figure dict over props, nothing to merge.
|
||||
tag.special_props.append(Var(_js_expr=f"{{...{figure!s}}}"))
|
||||
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")
|
||||
|
@ -11,6 +11,7 @@ from reflex.components.component import NoSSRComponent
|
||||
from reflex.event import EventType
|
||||
from reflex.style import Style
|
||||
from reflex.utils import console
|
||||
from reflex.utils.imports import ImportDict
|
||||
from reflex.vars.base import Var
|
||||
|
||||
try:
|
||||
@ -141,3 +142,767 @@ class Plotly(NoSSRComponent):
|
||||
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.
|
||||
"""
|
||||
...
|
||||
|
@ -144,7 +144,7 @@ class ColorModeIconButton(IconButton):
|
||||
|
||||
if allow_system:
|
||||
|
||||
def color_mode_item(_color_mode: str):
|
||||
def color_mode_item(_color_mode: Literal["light", "dark", "system"]):
|
||||
return dropdown_menu.item(
|
||||
_color_mode.title(), on_click=set_color_mode(_color_mode)
|
||||
)
|
||||
|
@ -8,6 +8,7 @@ from reflex.base import Base
|
||||
from reflex.components.component import Component, ComponentNamespace
|
||||
from reflex.components.lucide.icon import Icon
|
||||
from reflex.components.props import NoExtrasAllowedProps, PropsBase
|
||||
from reflex.constants.base import Dirs
|
||||
from reflex.event import EventSpec, run_script
|
||||
from reflex.style import Style, resolved_color_mode
|
||||
from reflex.utils import format
|
||||
@ -27,7 +28,10 @@ LiteralPosition = Literal[
|
||||
"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):
|
||||
@ -327,6 +331,19 @@ class Toaster(Component):
|
||||
"""
|
||||
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
|
||||
def toast_dismiss(id: Var | str | None = None):
|
||||
"""Dismiss a toast.
|
||||
@ -378,6 +395,7 @@ class ToastNamespace(ComponentNamespace):
|
||||
warning = staticmethod(Toaster.toast_warning)
|
||||
error = staticmethod(Toaster.toast_error)
|
||||
success = staticmethod(Toaster.toast_success)
|
||||
loading = staticmethod(Toaster.toast_loading)
|
||||
dismiss = staticmethod(Toaster.toast_dismiss)
|
||||
__call__ = staticmethod(Toaster.send_toast)
|
||||
|
||||
|
@ -9,9 +9,12 @@ from reflex.base import Base
|
||||
from reflex.components.component import Component, ComponentNamespace
|
||||
from reflex.components.lucide.icon import Icon
|
||||
from reflex.components.props import NoExtrasAllowedProps, PropsBase
|
||||
from reflex.constants.base import Dirs
|
||||
from reflex.event import EventSpec, EventType
|
||||
from reflex.style import Style
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.utils.serializers import serializer
|
||||
from reflex.vars import VarData
|
||||
from reflex.vars.base import Var
|
||||
|
||||
LiteralPosition = Literal[
|
||||
@ -22,7 +25,10 @@ LiteralPosition = Literal[
|
||||
"bottom-center",
|
||||
"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):
|
||||
label: str
|
||||
@ -70,6 +76,8 @@ class Toaster(Component):
|
||||
@staticmethod
|
||||
def toast_success(message: str | Var = "", **kwargs: Any): ...
|
||||
@staticmethod
|
||||
def toast_loading(message: str | Var = "", **kwargs: Any): ...
|
||||
@staticmethod
|
||||
def toast_dismiss(id: Var | str | None = None): ...
|
||||
@overload
|
||||
@classmethod
|
||||
@ -172,6 +180,7 @@ class ToastNamespace(ComponentNamespace):
|
||||
warning = staticmethod(Toaster.toast_warning)
|
||||
error = staticmethod(Toaster.toast_error)
|
||||
success = staticmethod(Toaster.toast_success)
|
||||
loading = staticmethod(Toaster.toast_loading)
|
||||
dismiss = staticmethod(Toaster.toast_dismiss)
|
||||
|
||||
@staticmethod
|
||||
|
@ -134,6 +134,10 @@ class IterTag(Tag):
|
||||
if isinstance(component, (Foreach, Cond)):
|
||||
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.
|
||||
if component.key is None:
|
||||
component.key = index
|
||||
|
@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
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.utils import format, types
|
||||
@ -103,7 +103,7 @@ class Tag:
|
||||
{
|
||||
format.to_camel_case(name, allow_hyphens=True): (
|
||||
prop
|
||||
if types._isinstance(prop, Union[EventChain, dict])
|
||||
if types._isinstance(prop, (EventChain, Mapping))
|
||||
else LiteralVar.create(prop)
|
||||
) # rx.color is always a string
|
||||
for name, prop in kwargs.items()
|
||||
|
186
reflex/config.py
186
reflex/config.py
@ -2,20 +2,25 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import concurrent.futures
|
||||
import dataclasses
|
||||
import enum
|
||||
import importlib
|
||||
import inspect
|
||||
import multiprocessing
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import threading
|
||||
import urllib.parse
|
||||
from functools import lru_cache
|
||||
from importlib.util import find_spec
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Generic,
|
||||
List,
|
||||
@ -23,10 +28,15 @@ from typing import (
|
||||
Set,
|
||||
TypeVar,
|
||||
get_args,
|
||||
get_origin,
|
||||
)
|
||||
|
||||
from reflex_cli.constants.hosting import Hosting
|
||||
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.types import GenericType, is_union, value_inside_optional
|
||||
|
||||
@ -35,11 +45,11 @@ try:
|
||||
except ModuleNotFoundError:
|
||||
import pydantic
|
||||
|
||||
from reflex_cli.constants.hosting import Hosting
|
||||
|
||||
from reflex import constants
|
||||
from reflex.base import Base
|
||||
from reflex.utils import console
|
||||
try:
|
||||
from dotenv import load_dotenv # pyright: ignore [reportMissingImports]
|
||||
except ImportError:
|
||||
load_dotenv = None
|
||||
|
||||
|
||||
class DBConfig(Base):
|
||||
@ -304,6 +314,15 @@ def interpret_env_var_value(
|
||||
return interpret_path_env(value, field_name)
|
||||
elif field_type is ExistingPath:
|
||||
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):
|
||||
return interpret_enum_env(value, field_type, field_name)
|
||||
|
||||
@ -387,7 +406,24 @@ class EnvVar(Generic[T]):
|
||||
else:
|
||||
if isinstance(value, enum.Enum):
|
||||
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]
|
||||
@ -416,7 +452,9 @@ class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
|
||||
"""
|
||||
self.name = name
|
||||
|
||||
def __get__(self, instance: Any, owner: Any):
|
||||
def __get__(
|
||||
self, instance: EnvironmentVariables, owner: type[EnvironmentVariables]
|
||||
):
|
||||
"""Get the EnvVar instance.
|
||||
|
||||
Args:
|
||||
@ -426,7 +464,7 @@ class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
|
||||
Returns:
|
||||
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
|
||||
if self.internal:
|
||||
env_name = f"__{env_name}"
|
||||
@ -463,6 +501,95 @@ class PerformanceMode(enum.Enum):
|
||||
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:
|
||||
"""Environment variables class to instantiate environment variables."""
|
||||
|
||||
@ -504,6 +631,8 @@ class EnvironmentVariables:
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
RELOAD_CONFIG: EnvVar[bool] = env_var(False, internal=True)
|
||||
|
||||
@ -563,7 +698,7 @@ class EnvironmentVariables:
|
||||
REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True)
|
||||
|
||||
# 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.
|
||||
REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000)
|
||||
@ -571,6 +706,12 @@ class EnvironmentVariables:
|
||||
# Whether to use the turbopack bundler.
|
||||
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()
|
||||
|
||||
@ -604,6 +745,7 @@ class Config(Base):
|
||||
"""Pydantic config for the config."""
|
||||
|
||||
validate_assignment = True
|
||||
use_enum_values = False
|
||||
|
||||
# The name of the app (should match the name of the app directory).
|
||||
app_name: str
|
||||
@ -615,19 +757,21 @@ class Config(Base):
|
||||
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.
|
||||
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
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
backend_host: str = "0.0.0.0"
|
||||
@ -679,7 +823,7 @@ class Config(Base):
|
||||
# Number of gunicorn workers from user
|
||||
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
|
||||
|
||||
# Variance limit for max requests; gunicorn only
|
||||
@ -734,6 +878,9 @@ class Config(Base):
|
||||
self._non_default_attributes.update(kwargs)
|
||||
self._replace_defaults(**kwargs)
|
||||
|
||||
# Set the log level for this process
|
||||
console.set_log_level(self.loglevel)
|
||||
|
||||
if (
|
||||
self.state_manager_mode == constants.StateManagerMode.REDIS
|
||||
and not self.redis_url
|
||||
@ -773,16 +920,15 @@ class Config(Base):
|
||||
Returns:
|
||||
The updated config values.
|
||||
"""
|
||||
if self.env_file:
|
||||
try:
|
||||
from dotenv import load_dotenv # pyright: ignore [reportMissingImports]
|
||||
|
||||
# load env file if exists
|
||||
load_dotenv(self.env_file, override=True)
|
||||
except ImportError:
|
||||
env_file = self.env_file or os.environ.get("ENV_FILE", None)
|
||||
if env_file:
|
||||
if load_dotenv is None:
|
||||
console.error(
|
||||
"""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 = {}
|
||||
# Iterate over the fields.
|
||||
|
@ -26,6 +26,7 @@ from typing import (
|
||||
|
||||
from typing_extensions import (
|
||||
Protocol,
|
||||
Self,
|
||||
TypeAliasType,
|
||||
TypedDict,
|
||||
TypeVar,
|
||||
@ -110,7 +111,7 @@ class EventActionsMixin:
|
||||
event_actions: Dict[str, Union[bool, int]] = dataclasses.field(default_factory=dict)
|
||||
|
||||
@property
|
||||
def stop_propagation(self):
|
||||
def stop_propagation(self) -> Self:
|
||||
"""Stop the event from bubbling up the DOM tree.
|
||||
|
||||
Returns:
|
||||
@ -122,7 +123,7 @@ class EventActionsMixin:
|
||||
)
|
||||
|
||||
@property
|
||||
def prevent_default(self):
|
||||
def prevent_default(self) -> Self:
|
||||
"""Prevent the default behavior of the event.
|
||||
|
||||
Returns:
|
||||
@ -133,7 +134,7 @@ class EventActionsMixin:
|
||||
event_actions={"preventDefault": True, **self.event_actions},
|
||||
)
|
||||
|
||||
def throttle(self, limit_ms: int):
|
||||
def throttle(self, limit_ms: int) -> Self:
|
||||
"""Throttle the event handler.
|
||||
|
||||
Args:
|
||||
@ -147,7 +148,7 @@ class EventActionsMixin:
|
||||
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.
|
||||
|
||||
Args:
|
||||
@ -162,7 +163,7 @@ class EventActionsMixin:
|
||||
)
|
||||
|
||||
@property
|
||||
def temporal(self):
|
||||
def temporal(self) -> Self:
|
||||
"""Do not queue the event if the backend is down.
|
||||
|
||||
Returns:
|
||||
@ -1773,7 +1774,7 @@ V4 = TypeVar("V4")
|
||||
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."""
|
||||
|
||||
def __init__(self, func: Callable[[Any, Unpack[P]], Any]):
|
||||
@ -1784,24 +1785,6 @@ class EventCallback(Generic[Unpack[P]]):
|
||||
"""
|
||||
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
|
||||
def __call__(
|
||||
self: EventCallback[Unpack[Q]],
|
||||
|
@ -127,8 +127,8 @@ def _run(
|
||||
env: constants.Env = constants.Env.DEV,
|
||||
frontend: bool = True,
|
||||
backend: bool = True,
|
||||
frontend_port: int = config.frontend_port,
|
||||
backend_port: int = config.backend_port,
|
||||
frontend_port: int | None = None,
|
||||
backend_port: int | None = None,
|
||||
backend_host: str = config.backend_host,
|
||||
loglevel: constants.LogLevel = config.loglevel,
|
||||
):
|
||||
@ -145,10 +145,7 @@ def _run(
|
||||
exec.output_system_info()
|
||||
|
||||
# If no --frontend-only and no --backend-only, then turn on frontend and backend both
|
||||
if not frontend and not backend:
|
||||
frontend = True
|
||||
backend = True
|
||||
|
||||
frontend, backend = prerequisites.check_running_mode(frontend, backend)
|
||||
if not frontend and backend:
|
||||
_skip_compile()
|
||||
|
||||
@ -161,17 +158,28 @@ def _run(
|
||||
|
||||
# Find the next available open port if applicable.
|
||||
if frontend:
|
||||
auto_increment_frontend = not bool(frontend_port or config.frontend_port)
|
||||
frontend_port = processes.handle_port(
|
||||
"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:
|
||||
auto_increment_backend = not bool(backend_port or config.backend_port)
|
||||
|
||||
backend_port = processes.handle_port(
|
||||
"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.
|
||||
@ -249,7 +257,7 @@ def _run(
|
||||
# Start the frontend and backend.
|
||||
with processes.run_concurrently_context(*commands):
|
||||
# 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_host, int(backend_port), loglevel.subprocess_level(), frontend
|
||||
)
|
||||
@ -278,10 +286,14 @@ def run(
|
||||
envvar=environment.REFLEX_BACKEND_ONLY.name,
|
||||
),
|
||||
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(
|
||||
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(
|
||||
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."
|
||||
),
|
||||
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(
|
||||
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(
|
||||
str(Path.cwd()),
|
||||
@ -332,7 +352,9 @@ def export(
|
||||
from reflex.utils import export as export_utils
|
||||
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)
|
||||
|
||||
if frontend and not config.show_built_with_reflex:
|
||||
|
@ -1747,6 +1747,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
||||
|
||||
Yields:
|
||||
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
|
||||
|
||||
@ -1784,12 +1787,25 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
||||
hinted_args, (Base, BaseModelV1, BaseModelV2)
|
||||
):
|
||||
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)
|
||||
if isinstance(value, list) and (
|
||||
elif isinstance(value, list) and (
|
||||
hinted_args is tuple or hinted_args is Tuple
|
||||
):
|
||||
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.
|
||||
try:
|
||||
@ -2464,6 +2480,8 @@ class ComponentState(State, mixin=True):
|
||||
Returns:
|
||||
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
|
||||
state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}"
|
||||
component_state = type(
|
||||
@ -2475,6 +2493,7 @@ class ComponentState(State, mixin=True):
|
||||
# Save a reference to the dynamic state for pickle/unpickle.
|
||||
setattr(reflex.istate.dynamic, state_cls_name, component_state)
|
||||
component = component_state.get_component(*children, **props)
|
||||
component = into_component(component)
|
||||
component.State = component_state
|
||||
return component
|
||||
|
||||
|
@ -12,7 +12,7 @@ from reflex.utils.exceptions import ReflexError
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.utils.types import get_origin
|
||||
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.object import ObjectVar
|
||||
|
||||
@ -48,7 +48,6 @@ def _color_mode_var(_js_expr: str, _var_type: Type = str) -> Var:
|
||||
).guess_type()
|
||||
|
||||
|
||||
@CallableVar
|
||||
def set_color_mode(
|
||||
new_color_mode: LiteralColorMode | Var[LiteralColorMode] | None = None,
|
||||
) -> Var[EventChain]:
|
||||
|
@ -43,6 +43,7 @@ import reflex.utils.exec
|
||||
import reflex.utils.format
|
||||
import reflex.utils.prerequisites
|
||||
import reflex.utils.processes
|
||||
from reflex.components.component import CustomComponent
|
||||
from reflex.config import environment
|
||||
from reflex.state import (
|
||||
BaseState,
|
||||
@ -254,6 +255,7 @@ class AppHarness:
|
||||
# disable telemetry reporting for tests
|
||||
|
||||
os.environ["TELEMETRY_ENABLED"] = "false"
|
||||
CustomComponent.create().get_component.cache_clear()
|
||||
self.app_path.mkdir(parents=True, exist_ok=True)
|
||||
if self.app_source is not None:
|
||||
app_globals = self._get_globals_from_signature(self.app_source)
|
||||
|
@ -2,8 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import inspect
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
from pathlib import Path
|
||||
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."
|
||||
)
|
||||
global _LOG_LEVEL
|
||||
if log_level != _LOG_LEVEL:
|
||||
# Set the loglevel persistenly for subprocesses.
|
||||
os.environ["LOGLEVEL"] = log_level.value
|
||||
_LOG_LEVEL = log_level
|
||||
|
||||
|
||||
@ -317,3 +323,20 @@ def status(*args, **kwargs):
|
||||
A new status.
|
||||
"""
|
||||
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
25
reflex/utils/decorator.py
Normal 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
|
@ -10,6 +10,7 @@ import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import psutil
|
||||
@ -242,29 +243,63 @@ def run_backend(
|
||||
run_uvicorn_backend(host, port, loglevel)
|
||||
|
||||
|
||||
def get_reload_dirs() -> list[Path]:
|
||||
"""Get the reload directories for the backend.
|
||||
def get_reload_paths() -> Sequence[Path]:
|
||||
"""Get the reload paths for the backend.
|
||||
|
||||
Returns:
|
||||
The reload directories for the backend.
|
||||
The reload paths for the backend.
|
||||
"""
|
||||
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__:
|
||||
module_path = Path(config.app_module.__file__).resolve().parent
|
||||
|
||||
while module_path.parent.name:
|
||||
if any(
|
||||
sibling_file.name == "__init__.py"
|
||||
for sibling_file in module_path.parent.iterdir()
|
||||
):
|
||||
# go up a level to find dir without `__init__.py`
|
||||
module_path = module_path.parent
|
||||
else:
|
||||
break
|
||||
while module_path.parent.name and any(
|
||||
sibling_file.name == "__init__.py"
|
||||
for sibling_file in module_path.parent.iterdir()
|
||||
):
|
||||
# go up a level to find dir without `__init__.py`
|
||||
module_path = module_path.parent
|
||||
|
||||
reload_dirs = [module_path]
|
||||
return reload_dirs
|
||||
reload_paths = [module_path]
|
||||
|
||||
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):
|
||||
@ -283,7 +318,7 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
|
||||
port=port,
|
||||
log_level=loglevel.value,
|
||||
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,
|
||||
log_level=LogLevels(loglevel.value),
|
||||
reload=True,
|
||||
reload_paths=get_reload_dirs(),
|
||||
reload_ignore_dirs=[".web", ".states"],
|
||||
reload_paths=get_reload_paths(),
|
||||
).serve()
|
||||
except ImportError:
|
||||
console.error(
|
||||
@ -368,34 +402,49 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
|
||||
|
||||
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 = (
|
||||
[
|
||||
*run_backend_prod_windows,
|
||||
"--host",
|
||||
host,
|
||||
"--port",
|
||||
str(port),
|
||||
"uvicorn",
|
||||
*(
|
||||
[
|
||||
"--limit-max-requests",
|
||||
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,
|
||||
]
|
||||
if constants.IS_WINDOWS
|
||||
else [
|
||||
*run_backend_prod,
|
||||
"--bind",
|
||||
f"{host}:{port}",
|
||||
"--threads",
|
||||
str(_get_backend_workers()),
|
||||
"gunicorn",
|
||||
*("--worker-class", config.gunicorn_worker_class),
|
||||
*(
|
||||
[
|
||||
"--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}()",
|
||||
]
|
||||
)
|
||||
|
||||
command += [
|
||||
"--log-level",
|
||||
loglevel.value,
|
||||
"--workers",
|
||||
str(_get_backend_workers()),
|
||||
*("--log-level", loglevel.value),
|
||||
]
|
||||
|
||||
processes.new_process(
|
||||
command,
|
||||
run=True,
|
||||
|
@ -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:
|
||||
"""Check if the given character is a valid brace.
|
||||
|
||||
|
@ -109,6 +109,9 @@ class ImportVar:
|
||||
# whether this import should be rendered or not
|
||||
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
|
||||
# https://nextjs.org/docs/app/api-reference/next-config-js/transpilePackages
|
||||
transpile: Optional[bool] = False
|
||||
|
@ -260,3 +260,49 @@ def find_replace(directory: str | Path, find: str, replace: str):
|
||||
text = filepath.read_text(encoding="utf-8")
|
||||
text = re.sub(find, replace, text)
|
||||
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)
|
||||
|
@ -24,6 +24,7 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Any, Callable, List, NamedTuple, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
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:
|
||||
"""Check if an app needs to be reinitialized.
|
||||
|
||||
@ -1302,10 +1318,13 @@ def validate_bun():
|
||||
"""
|
||||
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}")
|
||||
bun_version = get_bun_version()
|
||||
if not bun_version:
|
||||
if bun_version is None:
|
||||
console.error(
|
||||
"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
|
||||
else:
|
||||
template_parsed_url = urlparse(template)
|
||||
# Check if the template is a github repo.
|
||||
if template.startswith("https://github.com"):
|
||||
template_url = f"{template.strip('/').replace('.git', '')}/archive/main.zip"
|
||||
if template_parsed_url.hostname == "github.com":
|
||||
path = template_parsed_url.path.strip("/").removesuffix(".git")
|
||||
template_url = f"https://github.com/{path}/archive/main.zip"
|
||||
else:
|
||||
console.error(f"Template `{template}` not found or invalid.")
|
||||
raise typer.Exit(1)
|
||||
|
@ -116,17 +116,14 @@ def change_port(port: int, _type: str) -> int:
|
||||
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.
|
||||
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.
|
||||
Otherwise tell the user the port is in use and exit the app.
|
||||
|
||||
Args:
|
||||
service_name: The frontend or backend.
|
||||
port: The provided port.
|
||||
default_port: The default port number associated with the specified service.
|
||||
auto_increment: Whether to automatically increment the port.
|
||||
|
||||
Returns:
|
||||
The port to run the service on.
|
||||
@ -134,13 +131,15 @@ def handle_port(service_name: str, port: int, default_port: int) -> int:
|
||||
Raises:
|
||||
Exit:when the port is in use.
|
||||
"""
|
||||
if is_process_on_port(port):
|
||||
if port == int(default_port):
|
||||
return change_port(port, service_name)
|
||||
else:
|
||||
console.error(f"{service_name.capitalize()} port: {port} is already in use")
|
||||
raise typer.Exit()
|
||||
return port
|
||||
if (process := get_process_on_port(port)) is None:
|
||||
return port
|
||||
if auto_increment:
|
||||
return change_port(port, service_name)
|
||||
else:
|
||||
console.error(
|
||||
f"{service_name.capitalize()} port: {port} is already in use by PID: {process.pid}."
|
||||
)
|
||||
raise typer.Exit()
|
||||
|
||||
|
||||
def new_process(
|
||||
|
@ -95,6 +95,7 @@ GenericType = Union[Type, _GenericAlias]
|
||||
# Valid state var types.
|
||||
JSONType = {str, int, float, bool}
|
||||
PrimitiveType = Union[int, float, bool, str, list, dict, set, tuple]
|
||||
PrimitiveTypes = (int, float, bool, str, list, dict, set, tuple)
|
||||
StateVar = Union[PrimitiveType, Base, None]
|
||||
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)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
obj: The object to check.
|
||||
cls: The class to check against.
|
||||
nested: Whether the check is nested.
|
||||
nested: How many levels deep to check.
|
||||
|
||||
Returns:
|
||||
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:
|
||||
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):
|
||||
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):
|
||||
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)
|
||||
|
||||
if origin is None:
|
||||
@ -596,38 +606,40 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
|
||||
# cls is a simple generic class
|
||||
return isinstance(obj, origin)
|
||||
|
||||
if nested and args:
|
||||
if nested > 0 and args:
|
||||
if origin is list:
|
||||
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 args[-1] is Ellipsis:
|
||||
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 (
|
||||
isinstance(obj, tuple)
|
||||
and len(obj) == len(args)
|
||||
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):
|
||||
return isinstance(obj, dict) and all(
|
||||
_isinstance(key, args[0]) and _isinstance(value, args[1])
|
||||
if origin in (dict, Mapping, Breakpoints):
|
||||
return isinstance(obj, Mapping) and all(
|
||||
_isinstance(key, args[0], nested=nested - 1)
|
||||
and _isinstance(value, args[1], nested=nested - 1)
|
||||
for key, value in obj.items()
|
||||
)
|
||||
if origin is set:
|
||||
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:
|
||||
from reflex.vars import Field
|
||||
|
||||
if origin is Field:
|
||||
return _isinstance(obj, args[0])
|
||||
return _isinstance(obj, args[0], nested=nested)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -75,9 +75,9 @@ from reflex.utils.types import (
|
||||
if TYPE_CHECKING:
|
||||
from reflex.state import BaseState
|
||||
|
||||
from .number import BooleanVar, NumberVar
|
||||
from .object import ObjectVar
|
||||
from .sequence import ArrayVar, StringVar
|
||||
from .number import BooleanVar, LiteralBooleanVar, LiteralNumberVar, NumberVar
|
||||
from .object import LiteralObjectVar, ObjectVar
|
||||
from .sequence import ArrayVar, LiteralArrayVar, LiteralStringVar, StringVar
|
||||
|
||||
|
||||
VAR_TYPE = TypeVar("VAR_TYPE", covariant=True)
|
||||
@ -573,13 +573,21 @@ class Var(Generic[VAR_TYPE]):
|
||||
|
||||
return value_with_replaced
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # pyright: ignore[reportOverlappingOverload]
|
||||
cls,
|
||||
value: NoReturn,
|
||||
_var_data: VarData | None = None,
|
||||
) -> Var[Any]: ...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # pyright: ignore[reportOverlappingOverload]
|
||||
cls,
|
||||
value: bool,
|
||||
_var_data: VarData | None = None,
|
||||
) -> BooleanVar: ...
|
||||
) -> LiteralBooleanVar: ...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
@ -587,7 +595,7 @@ class Var(Generic[VAR_TYPE]):
|
||||
cls,
|
||||
value: int,
|
||||
_var_data: VarData | None = None,
|
||||
) -> NumberVar[int]: ...
|
||||
) -> LiteralNumberVar[int]: ...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
@ -595,7 +603,15 @@ class Var(Generic[VAR_TYPE]):
|
||||
cls,
|
||||
value: float,
|
||||
_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
|
||||
@classmethod
|
||||
@ -611,7 +627,7 @@ class Var(Generic[VAR_TYPE]):
|
||||
cls,
|
||||
value: None,
|
||||
_var_data: VarData | None = None,
|
||||
) -> NoneVar: ...
|
||||
) -> LiteralNoneVar: ...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
@ -619,7 +635,7 @@ class Var(Generic[VAR_TYPE]):
|
||||
cls,
|
||||
value: MAPPING_TYPE,
|
||||
_var_data: VarData | None = None,
|
||||
) -> ObjectVar[MAPPING_TYPE]: ...
|
||||
) -> LiteralObjectVar[MAPPING_TYPE]: ...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
@ -627,7 +643,7 @@ class Var(Generic[VAR_TYPE]):
|
||||
cls,
|
||||
value: SEQUENCE_TYPE,
|
||||
_var_data: VarData | None = None,
|
||||
) -> ArrayVar[SEQUENCE_TYPE]: ...
|
||||
) -> LiteralArrayVar[SEQUENCE_TYPE]: ...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
@ -935,7 +951,7 @@ class Var(Generic[VAR_TYPE]):
|
||||
"""
|
||||
actual_name = self._var_field_name
|
||||
|
||||
def setter(state: BaseState, value: Any):
|
||||
def setter(state: Any, value: Any):
|
||||
"""Get the setter for the var.
|
||||
|
||||
Args:
|
||||
@ -953,6 +969,8 @@ class Var(Generic[VAR_TYPE]):
|
||||
else:
|
||||
setattr(state, actual_name, value)
|
||||
|
||||
setter.__annotations__["value"] = self._var_type
|
||||
|
||||
setter.__qualname__ = self._get_setter_name()
|
||||
|
||||
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")
|
||||
|
||||
DICT_KEY = TypeVar("DICT_KEY")
|
||||
@ -2238,6 +2201,27 @@ class ComputedVar(Var[RETURN_TYPE]):
|
||||
owner: Type,
|
||||
) -> 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
|
||||
def __get__(self, instance: None, owner: Type) -> ComputedVar[RETURN_TYPE]: ...
|
||||
|
||||
@ -2484,6 +2468,27 @@ class AsyncComputedVar(ComputedVar[RETURN_TYPE]):
|
||||
owner: Type,
|
||||
) -> 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
|
||||
def __get__(self, instance: None, owner: Type) -> AsyncComputedVar[RETURN_TYPE]: ...
|
||||
|
||||
|
@ -974,7 +974,7 @@ def boolean_not_operation(value: BooleanVar):
|
||||
frozen=True,
|
||||
slots=True,
|
||||
)
|
||||
class LiteralNumberVar(LiteralVar, NumberVar):
|
||||
class LiteralNumberVar(LiteralVar, NumberVar[NUMBER_T]):
|
||||
"""Base class for immutable literal number vars."""
|
||||
|
||||
_var_value: float | int = dataclasses.field(default=0)
|
||||
|
@ -22,7 +22,12 @@ from typing_extensions import is_typeddict
|
||||
|
||||
from reflex.utils import types
|
||||
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 (
|
||||
CachedVarOperation,
|
||||
@ -187,10 +192,14 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping):
|
||||
Returns:
|
||||
The item from the object.
|
||||
"""
|
||||
from .sequence import LiteralStringVar
|
||||
|
||||
if not isinstance(key, (StringVar, str, int, NumberVar)) or (
|
||||
isinstance(key, NumberVar) and key._is_strict_float()
|
||||
):
|
||||
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()
|
||||
|
||||
# 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):
|
||||
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 (
|
||||
(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 is_typeddict(fixed_type)
|
||||
):
|
||||
attribute_type = get_attribute_access_type(var_type, name)
|
||||
if attribute_type is None:
|
||||
|
@ -372,6 +372,33 @@ class StringVar(Var[STRING_TYPE], python_types=str):
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
@ -583,7 +610,7 @@ def string_replace_operation(
|
||||
The string replace operation.
|
||||
"""
|
||||
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,
|
||||
)
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
from .fixtures import evaluated_page
|
||||
from .fixtures import evaluated_page, unevaluated_page
|
||||
|
||||
__all__ = ["evaluated_page"]
|
||||
__all__ = ["evaluated_page", "unevaluated_page"]
|
||||
|
@ -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."
|
||||
|
||||
|
||||
@ -233,6 +302,82 @@ def _complicated_page():
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(params=[_simple_page, _complicated_page])
|
||||
def evaluated_page(request):
|
||||
def _counter():
|
||||
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()
|
||||
|
@ -1,18 +1,25 @@
|
||||
import pytest
|
||||
from pytest_codspeed import BenchmarkFixture
|
||||
|
||||
from reflex.compiler.compiler import _compile_page, _compile_stateful_components
|
||||
from reflex.components.component import Component
|
||||
|
||||
|
||||
@pytest.mark.benchmark
|
||||
def test_compile_page(evaluated_page):
|
||||
_compile_page(evaluated_page, None)
|
||||
def import_templates():
|
||||
# Importing the templates module to avoid the import time in the benchmark
|
||||
import reflex.compiler.templates # noqa: F401
|
||||
|
||||
|
||||
@pytest.mark.benchmark
|
||||
def test_compile_stateful(evaluated_page):
|
||||
_compile_stateful_components([evaluated_page])
|
||||
def test_compile_page(evaluated_page: Component, benchmark: BenchmarkFixture):
|
||||
import_templates()
|
||||
|
||||
benchmark(lambda: _compile_page(evaluated_page, None))
|
||||
|
||||
|
||||
@pytest.mark.benchmark
|
||||
def test_get_all_imports(evaluated_page):
|
||||
evaluated_page._get_all_imports()
|
||||
def test_compile_stateful(evaluated_page: Component, benchmark: BenchmarkFixture):
|
||||
import_templates()
|
||||
|
||||
benchmark(lambda: _compile_stateful_components([evaluated_page]))
|
||||
|
||||
|
||||
def test_get_all_imports(evaluated_page: Component, benchmark: BenchmarkFixture):
|
||||
benchmark(lambda: evaluated_page._get_all_imports())
|
||||
|
@ -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
|
||||
@pytest.mark.parametrize("page", [_simple_page, _complicated_page])
|
||||
def test_evaluate_page(page):
|
||||
page()
|
||||
def test_evaluate_page(
|
||||
unevaluated_page: Callable[[], Component], benchmark: BenchmarkFixture
|
||||
):
|
||||
benchmark(unevaluated_page)
|
||||
|
@ -20,7 +20,11 @@ def BackgroundTask():
|
||||
class State(rx.State):
|
||||
counter: 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)
|
||||
async def handle_event(self):
|
||||
@ -125,8 +129,8 @@ def BackgroundTask():
|
||||
rx.input(
|
||||
id="iterations",
|
||||
placeholder="Iterations",
|
||||
value=State.iterations.to_string(), # pyright: ignore [reportAttributeAccessIssue]
|
||||
on_change=State.set_iterations, # pyright: ignore [reportAttributeAccessIssue]
|
||||
value=State.iterations.to_string(),
|
||||
on_change=State.set_iterations,
|
||||
),
|
||||
rx.button(
|
||||
"Delayed Increment",
|
||||
|
@ -87,7 +87,7 @@ def UploadFile():
|
||||
),
|
||||
rx.box(
|
||||
rx.foreach(
|
||||
rx.selected_files,
|
||||
rx.selected_files(),
|
||||
lambda f: rx.text(f, as_="p"),
|
||||
),
|
||||
id="selected_files",
|
||||
|
@ -10,6 +10,8 @@ from reflex.testing import AppHarness
|
||||
|
||||
def VarOperations():
|
||||
"""App with var operations."""
|
||||
from typing import TypedDict
|
||||
|
||||
import reflex as rx
|
||||
from reflex.vars.base import LiteralVar
|
||||
from reflex.vars.sequence import ArrayVar
|
||||
@ -17,6 +19,10 @@ def VarOperations():
|
||||
class Object(rx.Base):
|
||||
name: str = "hello"
|
||||
|
||||
class Person(TypedDict):
|
||||
name: str
|
||||
age: int
|
||||
|
||||
class VarOperationState(rx.State):
|
||||
int_var1: rx.Field[int] = rx.field(10)
|
||||
int_var2: rx.Field[int] = rx.field(5)
|
||||
@ -34,6 +40,9 @@ def VarOperations():
|
||||
dict1: rx.Field[dict[int, int]] = rx.field({1: 2})
|
||||
dict2: rx.Field[dict[int, int]] = rx.field({3: 4})
|
||||
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)
|
||||
|
||||
@ -619,6 +628,23 @@ def VarOperations():
|
||||
),
|
||||
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_foreach2", "12"),
|
||||
("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:
|
||||
|
@ -61,7 +61,7 @@ def ColorToggleApp():
|
||||
rx.icon(tag="moon", size=20),
|
||||
value="dark",
|
||||
),
|
||||
on_change=set_color_mode,
|
||||
on_change=set_color_mode(),
|
||||
variant="classic",
|
||||
radius="large",
|
||||
value=color_mode,
|
||||
|
@ -11,6 +11,7 @@ from reflex.components.lucide.icon import Icon
|
||||
from reflex.components.radix.themes.layout.box import Box
|
||||
from reflex.style import Style
|
||||
from reflex.vars import Var
|
||||
from reflex.vars.base import LiteralVar
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -99,7 +100,9 @@ def test_create_shiki_code_block(
|
||||
|
||||
applied_styles = component.style
|
||||
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(
|
||||
|
@ -148,7 +148,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe
|
||||
(
|
||||
"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",
|
||||
@ -157,7 +157,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe
|
||||
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",
|
||||
@ -171,7 +171,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe
|
||||
(
|
||||
"code",
|
||||
{"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",
|
||||
@ -180,11 +180,12 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe
|
||||
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):
|
||||
markdown = Markdown.create("# header", component_map=component_map)
|
||||
result = markdown.format_component_map()
|
||||
print(str(result[key]))
|
||||
assert str(result[key]) == expected
|
||||
|
@ -1299,6 +1299,7 @@ def test_app_wrap_compile_theme(
|
||||
app_js_lines = [
|
||||
line.strip() for line in app_js_contents.splitlines() if line.strip()
|
||||
]
|
||||
lines = "".join(app_js_lines)
|
||||
assert (
|
||||
"function AppWrap({children}) {"
|
||||
"return ("
|
||||
@ -1306,14 +1307,17 @@ def test_app_wrap_compile_theme(
|
||||
+ "<RadixThemesColorModeProvider>"
|
||||
"<RadixThemesTheme accentColor={\"plum\"} css={{...theme.styles.global[':root'], ...theme.styles.global.body}}>"
|
||||
"<Fragment>"
|
||||
"<MemoizedToastProvider/>"
|
||||
"<Fragment>"
|
||||
"{children}"
|
||||
"</Fragment>"
|
||||
"</Fragment>"
|
||||
"</RadixThemesTheme>"
|
||||
"</RadixThemesColorModeProvider>"
|
||||
+ ("</StrictMode>" if react_strict_mode else "")
|
||||
+ ")"
|
||||
"}"
|
||||
) in "".join(app_js_lines)
|
||||
) in lines
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -1362,6 +1366,7 @@ def test_app_wrap_priority(
|
||||
app_js_lines = [
|
||||
line.strip() for line in app_js_contents.splitlines() if line.strip()
|
||||
]
|
||||
lines = "".join(app_js_lines)
|
||||
assert (
|
||||
"function AppWrap({children}) {"
|
||||
"return (" + ("<StrictMode>" if react_strict_mode else "") + "<RadixThemesBox>"
|
||||
@ -1369,14 +1374,16 @@ def test_app_wrap_priority(
|
||||
"<RadixThemesColorModeProvider>"
|
||||
"<Fragment2>"
|
||||
"<Fragment>"
|
||||
"<MemoizedToastProvider/>"
|
||||
"<Fragment>"
|
||||
"{children}"
|
||||
"</Fragment>"
|
||||
"</Fragment>"
|
||||
"</Fragment2>"
|
||||
"</RadixThemesColorModeProvider>"
|
||||
"</RadixThemesText>"
|
||||
"</RadixThemesBox>" + ("</StrictMode>" if react_strict_mode else "") + ")"
|
||||
"}"
|
||||
) in "".join(app_js_lines)
|
||||
"</RadixThemesBox>" + ("</StrictMode>" if react_strict_mode else "")
|
||||
) in lines
|
||||
|
||||
|
||||
def test_app_state_determination():
|
||||
|
@ -252,6 +252,7 @@ def test_env_var():
|
||||
BLUBB: EnvVar[str] = env_var("default")
|
||||
INTERNAL: EnvVar[str] = env_var("default", internal=True)
|
||||
BOOLEAN: EnvVar[bool] = env_var(False)
|
||||
LIST: EnvVar[list[int]] = env_var([1, 2, 3])
|
||||
|
||||
assert TestEnv.BLUBB.get() == "default"
|
||||
assert TestEnv.BLUBB.name == "BLUBB"
|
||||
@ -280,3 +281,11 @@ def test_env_var():
|
||||
assert TestEnv.BOOLEAN.get() is False
|
||||
TestEnv.BOOLEAN.set(None)
|
||||
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
|
||||
|
@ -8,6 +8,7 @@ from pandas import DataFrame
|
||||
|
||||
import reflex as rx
|
||||
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.state import BaseState
|
||||
from reflex.utils.exceptions import (
|
||||
@ -1893,3 +1894,27 @@ def test_var_data_hooks():
|
||||
def test_var_data_with_hooks_value():
|
||||
var_data = VarData(hooks={"what": VarData(hooks={"whot": VarData(hooks="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),
|
||||
)
|
||||
|
@ -115,7 +115,20 @@ def test_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
|
||||
or does not exist.
|
||||
|
||||
@ -123,13 +136,12 @@ def test_validate_invalid_bun_path(mocker):
|
||||
mocker: Pytest mocker object.
|
||||
"""
|
||||
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.samefile", return_value=False)
|
||||
mocker.patch("reflex.utils.prerequisites.get_bun_version", return_value=None)
|
||||
|
||||
with pytest.raises(typer.Exit):
|
||||
prerequisites.validate_bun()
|
||||
mock_path.samefile.assert_called_once()
|
||||
|
||||
|
||||
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.samefile.return_value = False
|
||||
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=version.parse("0.6.5"),
|
||||
|
@ -74,11 +74,11 @@ class ObjectState(rx.State):
|
||||
|
||||
|
||||
@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_()
|
||||
var = Var.create(my_object)
|
||||
assert var._var_type is type_
|
||||
|
||||
assert isinstance(var, ObjectVar)
|
||||
quantity = var.quantity
|
||||
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])
|
||||
def test_guess(type_: GenericType) -> None:
|
||||
def test_guess(type_: type[Base | Bare | SqlaModel | Dataclass]) -> None:
|
||||
my_object = type_()
|
||||
var = Var.create(my_object)
|
||||
var = var.guess_type()
|
||||
assert var._var_type is type_
|
||||
|
||||
assert isinstance(var, ObjectVar)
|
||||
quantity = var.quantity
|
||||
assert quantity._var_type is int
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user