Merge branch 'main' into lendemor/easy_switch_between_state_manager

This commit is contained in:
Lendemor 2024-10-10 19:09:18 +02:00
commit 447527da9e
88 changed files with 2430 additions and 1337 deletions

View File

@ -18,7 +18,7 @@ inputs:
poetry-version:
description: 'Poetry version to install'
required: false
default: '1.3.1'
default: '1.8.3'
run-poetry-install:
description: 'Whether to run poetry install on current dir'
required: false

View File

@ -0,0 +1,88 @@
name: check-outdated-dependencies
on:
push: # This will trigger the action when a pull request is opened or updated.
branches:
- 'release/**' # This will trigger the action when any branch starting with "release/" is created.
workflow_dispatch: # Allow manual triggering if needed.
jobs:
backend:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- uses: ./.github/actions/setup_build_env
with:
python-version: '3.9'
run-poetry-install: true
create-venv-at-path: .venv
- name: Check outdated backend dependencies
run: |
outdated=$(poetry show -oT)
echo "Outdated:"
echo "$outdated"
filtered_outdated=$(echo "$outdated" | grep -vE 'pyright|ruff' || true)
if [ ! -z "$filtered_outdated" ]; then
echo "Outdated dependencies found:"
echo "$filtered_outdated"
exit 1
else
echo "All dependencies are up to date. (pyright and ruff are ignored)"
fi
frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
with:
python-version: '3.10.11'
run-poetry-install: true
create-venv-at-path: .venv
- name: Clone Reflex Website Repo
uses: actions/checkout@v4
with:
repository: reflex-dev/reflex-web
ref: main
path: reflex-web
- name: Install Requirements for reflex-web
working-directory: ./reflex-web
run: poetry run uv pip install -r requirements.txt
- name: Install additional dependencies for DB access
run: poetry run uv pip install psycopg2-binary
- name: Init Website for reflex-web
working-directory: ./reflex-web
run: poetry run reflex init
- name: Run Website and Check for errors
run: |
poetry run bash scripts/integration.sh ./reflex-web dev
- name: Check outdated frontend dependencies
working-directory: ./reflex-web/.web
run: |
raw_outdated=$(/home/runner/.local/share/reflex/bun/bin/bun outdated)
outdated=$(echo "$raw_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\|' || true)
echo "Outdated:"
echo "$outdated"
# Ignore 3rd party dependencies that are not updated.
filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw' || true)
no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
if [ ! -z "$no_extra" ]; then
echo "Outdated dependencies found:"
echo "$filtered_outdated"
exit 1
else
echo "All dependencies are up to date. (3rd party packages are ignored)"
fi

View File

@ -3,7 +3,7 @@ fail_fast: true
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.4.10
rev: v0.6.9
hooks:
- id: ruff-format
args: [reflex, tests]
@ -25,7 +25,7 @@ repos:
rev: v1.1.313
hooks:
- id: pyright
args: [integration, reflex, tests]
args: [reflex, tests]
language: system
- repo: https://github.com/terrencepreilly/darglint

View File

@ -17,7 +17,7 @@
---
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md)
---

View File

@ -3,8 +3,8 @@
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
from utils import send_data_to_posthog
@ -28,7 +28,7 @@ def insert_benchmarking_data(
send_data_to_posthog("lighthouse_benchmark", properties)
def get_lighthouse_scores(directory_path: str) -> dict:
def get_lighthouse_scores(directory_path: str | Path) -> dict:
"""Extracts the Lighthouse scores from the JSON files in the specified directory.
Args:
@ -38,24 +38,21 @@ def get_lighthouse_scores(directory_path: str) -> dict:
dict: The Lighthouse scores.
"""
scores = {}
directory_path = Path(directory_path)
try:
for filename in os.listdir(directory_path):
if filename.endswith(".json") and filename != "manifest.json":
file_path = os.path.join(directory_path, filename)
with open(file_path, "r") as file:
data = json.load(file)
# Extract scores and add them to the dictionary with the filename as key
scores[data["finalUrl"].replace("http://localhost:3000/", "/")] = {
"performance_score": data["categories"]["performance"]["score"],
"accessibility_score": data["categories"]["accessibility"][
"score"
],
"best_practices_score": data["categories"]["best-practices"][
"score"
],
"seo_score": data["categories"]["seo"]["score"],
}
for filename in directory_path.iterdir():
if filename.suffix == ".json" and filename.stem != "manifest":
file_path = directory_path / filename
data = json.loads(file_path.read_text())
# Extract scores and add them to the dictionary with the filename as key
scores[data["finalUrl"].replace("http://localhost:3000/", "/")] = {
"performance_score": data["categories"]["performance"]["score"],
"accessibility_score": data["categories"]["accessibility"]["score"],
"best_practices_score": data["categories"]["best-practices"][
"score"
],
"seo_score": data["categories"]["seo"]["score"],
}
except Exception as e:
return {"error": e}

View File

@ -2,11 +2,12 @@
import argparse
import os
from pathlib import Path
from utils import get_directory_size, get_python_version, send_data_to_posthog
def get_package_size(venv_path, os_name):
def get_package_size(venv_path: Path, os_name):
"""Get the size of a specified package.
Args:
@ -26,14 +27,12 @@ def get_package_size(venv_path, os_name):
is_windows = "windows" in os_name
full_path = (
["lib", f"python{python_version}", "site-packages"]
package_dir: Path = (
venv_path / "lib" / f"python{python_version}" / "site-packages"
if not is_windows
else ["Lib", "site-packages"]
else venv_path / "Lib" / "site-packages"
)
package_dir = os.path.join(venv_path, *full_path)
if not os.path.exists(package_dir):
if not package_dir.exists():
raise ValueError(
"Error: Virtual environment does not exist or is not activated."
)
@ -63,9 +62,9 @@ def insert_benchmarking_data(
path: The path to the dir or file to check size.
"""
if "./dist" in path:
size = get_directory_size(path)
size = get_directory_size(Path(path))
else:
size = get_package_size(path, os_type_version)
size = get_package_size(Path(path), os_type_version)
# Prepare the event data
properties = {

View File

@ -2,6 +2,7 @@
import argparse
import os
from pathlib import Path
from utils import get_directory_size, send_data_to_posthog
@ -28,7 +29,7 @@ def insert_benchmarking_data(
pr_id: The id of the PR.
path: The path to the dir or file to check size.
"""
size = get_directory_size(path)
size = get_directory_size(Path(path))
# Prepare the event data
properties = {

View File

@ -2,12 +2,13 @@
import os
import subprocess
from pathlib import Path
import httpx
from httpx import HTTPError
def get_python_version(venv_path, os_name):
def get_python_version(venv_path: Path, os_name):
"""Get the python version of python in a virtual env.
Args:
@ -18,13 +19,13 @@ def get_python_version(venv_path, os_name):
The python version.
"""
python_executable = (
os.path.join(venv_path, "bin", "python")
venv_path / "bin" / "python"
if "windows" not in os_name
else os.path.join(venv_path, "Scripts", "python.exe")
else venv_path / "Scripts" / "python.exe"
)
try:
output = subprocess.check_output(
[python_executable, "--version"], stderr=subprocess.STDOUT
[str(python_executable), "--version"], stderr=subprocess.STDOUT
)
python_version = output.decode("utf-8").strip().split()[1]
return ".".join(python_version.split(".")[:-1])
@ -32,7 +33,7 @@ def get_python_version(venv_path, os_name):
return None
def get_directory_size(directory):
def get_directory_size(directory: Path):
"""Get the size of a directory in bytes.
Args:
@ -44,8 +45,8 @@ def get_directory_size(directory):
total_size = 0
for dirpath, _, filenames in os.walk(directory):
for f in filenames:
fp = os.path.join(dirpath, f)
total_size += os.path.getsize(fp)
fp = Path(dirpath) / f
total_size += fp.stat().st_size
return total_size

267
docs/vi/README.md Normal file
View File

@ -0,0 +1,267 @@
```diff
+ Bạn đang tìm kiếm Pynecone? Bạn đã tìm đúng. Pynecone đã được đổi tên thành Reflex. +
```
<div align="center">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
<hr>
### **✨ Ứng dụng web hiệu suất cao, tùy chỉnh bằng Python thuần. Deploy trong vài giây. ✨**
[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex)
![versions](https://img.shields.io/pypi/pyversions/reflex.svg)
[![Documentation](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction)
[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ)
</div>
---
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md)
---
# Reflex
Reflex là một thư viện để xây dựng ứng dụng web toàn bộ bằng Python thuần.
Các tính năng chính:
* **Python thuần tuý** - Viết toàn bộ ứng dụng cả backend và frontend hoàn toàn bằng Python, không cần học JavaScript.
* **Full Flexibility** - Reflex dễ dàng để bắt đầu, nhưng cũng có thể mở rộng lên các ứng dụng phức tạp.
* **Deploy Instantly** - Sau khi xây dựng ứng dụng, bạn có thể triển khai bằng [một dòng lệnh](https://reflex.dev/docs/hosting/deploy-quick-start/) hoặc triển khai trên server của riêng bạn.
Đọc [bài viết về kiến trúc hệ thống](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) để hiểu rõ các hoạt động của Reflex.
## ⚙️ Cài đặt
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.9+):
```bash
pip install reflex
```
## 🥳 Tạo ứng dụng đầu tiên
Cài đặt `reflex` cũng như cài đặt công cụ dòng lệnh `reflex`.
Kiểm tra việc cài đặt đã thành công hay chưa bằng cách tạo mới một ứng dụng. (Thay `my_app_name` bằng tên ứng dụng của bạn):
```bash
mkdir my_app_name
cd my_app_name
reflex init
```
Lệnh này tạo ra một ứng dụng mẫu trong một thư mục mới.
Bạn có thể chạy ứng dụng ở chế độ phát triển.
```bash
reflex run
```
Bạn có thể xem ứng dụng của bạn ở địa chỉ http://localhost:3000.
Bạn có thể thay đổi mã nguồn ở `my_app_name/my_app_name.py`. Reflex nhanh chóng làm mới và bạn có thể thấy thay đổi trên ứng dụng của bạn ngay lập tức khi bạn lưu file.
## 🫧 Ứng dụng ví dụ
Bắt đầu với ví dụ: tạo một ứng dụng tạo ảnh bằng [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Để cho đơn giản, chúng ta sẽ sử dụng [OpenAI API](https://platform.openai.com/docs/api-reference/authentication), nhưng bạn có thể sử dụng model của chính bạn được triển khai trên local.
&nbsp;
<div align="center">
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="A frontend wrapper for DALL·E, shown in the process of generating an image." width="550" />
</div>
&nbsp;
Đây là toàn bộ đoạn mã để xây dựng ứng dụng trên. Nó được viết hoàn toàn trong một file Python!
```python
import reflex as rx
import openai
openai_client = openai.OpenAI()
class State(rx.State):
"""The app state."""
prompt = ""
image_url = ""
processing = False
complete = False
def get_image(self):
"""Get the image from the prompt."""
if self.prompt == "":
return rx.window_alert("Prompt Empty")
self.processing, self.complete = True, False
yield
response = openai_client.images.generate(
prompt=self.prompt, n=1, size="1024x1024"
)
self.image_url = response.data[0].url
self.processing, self.complete = False, True
def index():
return rx.center(
rx.vstack(
rx.heading("DALL-E", font_size="1.5em"),
rx.input(
placeholder="Enter a prompt..",
on_blur=State.set_prompt,
width="25em",
),
rx.button(
"Generate Image",
on_click=State.get_image,
width="25em",
loading=State.processing
),
rx.cond(
State.complete,
rx.image(src=State.image_url, width="20em"),
),
align="center",
),
width="100%",
height="100vh",
)
# Add state and page to the app.
app = rx.App()
app.add_page(index, title="Reflex:DALL-E")
```
## Hãy phân tích chi tiết.
<div align="center">
<img src="../images/dalle_colored_code_example.png" alt="Explaining the differences between backend and frontend parts of the DALL-E app." width="900" />
</div>
### **Reflex UI**
Bắt đầu với giao diện chính.
```python
def index():
return rx.center(
...
)
```
Hàm `index` định nghĩa phần giao diện chính của ứng dụng.
Chúng tôi sử dụng các component (thành phần) khác nhau như `center`, `vstack`, `input``button` để xây dựng giao diện phía trước.
Các component có thể được lồng vào nhau để tạo ra các bố cục phức tạp. Và bạn cũng có thể sử dụng từ khoá `args` để tận dụng đầy đủ sức mạnh của CSS.
Reflex có đến hơn [60 component được xây dựng sẵn](https://reflex.dev/docs/library) để giúp bạn bắt đầu. Chúng ta có thể tạo ra một component mới khá dễ dàng, thao khảo: [xây dựng component của riêng bạn](https://reflex.dev/docs/wrapping-react/overview/).
### **State**
Reflex biểu diễn giao diện bằng các hàm của state (trạng thái).
```python
class State(rx.State):
"""The app state."""
prompt = ""
image_url = ""
processing = False
complete = False
```
Một state định nghĩa các biến (được gọi là vars) có thể thay đổi trong một ứng dụng và cho phép các hàm có thể thay đổi chúng.
Tại đây state được cấu thành từ một `prompt``image_url`.
Có cũng những biến boolean `processing``complete`
để chỉ ra khi nào tắt nút (trong quá trình tạo hình ảnh)
và khi nào hiển thị hình ảnh kết quả.
### **Event Handlers**
```python
def get_image(self):
"""Get the image from the prompt."""
if self.prompt == "":
return rx.window_alert("Prompt Empty")
self.processing, self.complete = True, False
yield
response = openai_client.images.generate(
prompt=self.prompt, n=1, size="1024x1024"
)
self.image_url = response.data[0].url
self.processing, self.complete = False, True
```
Với các state, chúng ta định nghĩa các hàm có thể thay đổi state vars được gọi là event handlers. Event handler là cách chúng ta có thể thay đổi state trong Reflex. Chúng có thể là phản hồi khi người dùng thao tác, chằng hạn khi nhấn vào nút hoặc khi đang nhập trong text box. Các hành động này được gọi là event.
Ứng dụng DALL·E. của chúng ta có một event handler, `get_image` để lấy hình ảnh từ OpenAI API. Sử dụng từ khoá `yield` in ở giữa event handler để cập nhật giao diện. Hoặc giao diện có thể cập nhật ở cuối event handler.
### **Routing**
Cuối cùng, chúng ta định nghĩa một ứng dụng.
```python
app = rx.App()
```
Chúng ta thêm một trang ở đầu ứng dụng bằng index component. Chúng ta cũng thêm tiêu đề của ứng dụng để hiển thị lên trình duyệt.
```python
app.add_page(index, title="DALL-E")
```
Bạn có thể tạo một ứng dụng nhiều trang bằng cách thêm trang.
## 📑 Tài liệu
<div align="center">
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) &nbsp; | &nbsp; 🗞️ [Blog](https://reflex.dev/blog) &nbsp; | &nbsp; 📱 [Component Library](https://reflex.dev/docs/library) &nbsp; | &nbsp; 🖼️ [Gallery](https://reflex.dev/docs/gallery) &nbsp; | &nbsp; 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start) &nbsp;
</div>
## ✅ Status
Reflex phát hành vào tháng 12/2022 với tên là Pynecone.
Đến tháng 02/2024, chúng tôi tạo ra dịch vụ dưới phiên bản alpha! Trong thời gian này mọi người có thể triển khai ứng dụng hoàn toàn miễn phí. Xem [roadmap](https://github.com/reflex-dev/reflex/issues/2727) để biết thêm chi tiết.
Reflex ra phiên bản mới với các tính năng mới hàng tuần! Hãy :star: star và :eyes: watch repo này để thấy các cập nhật mới nhất.
## Contributing
Chúng tôi chào đón mọi đóng góp dù lớn hay nhỏ. Dưới đây là các cách để bắt đầu với cộng đồng Reflex.
- **Discord**: [Discord](https://discord.gg/T5WSbC2YtQ) của chúng tôi là nơi tốt nhất để nhờ sự giúp đỡ và thảo luận các bạn có thể đóng góp.
- **GitHub Discussions**: Là cách tốt nhất để thảo luận về các tính năng mà bạn có thể đóng góp hoặc những điều bạn chưa rõ.
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) là nơi tốt nhất để thông báo. Ngoài ra bạn có thể sửa chữa các vấn đề bằng cách tạo PR.
Chúng tôi luôn sẵn sàng tìm kiếm các contributor, bất kể kinh nghiệm. Để tham gia đóng góp, xin mời xem
[CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
## Xin cảm ơn các Contributors:
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
</a>
## License
Reflex là mã nguồn mở và sử dụng giấy phép [Apache License 2.0](LICENSE).

304
poetry.lock generated
View File

@ -121,13 +121,13 @@ files = [
[[package]]
name = "build"
version = "1.2.2"
version = "1.2.2.post1"
description = "A simple, correct Python build frontend"
optional = false
python-versions = ">=3.8"
files = [
{file = "build-1.2.2-py3-none-any.whl", hash = "sha256:277ccc71619d98afdd841a0e96ac9fe1593b823af481d3b0cea748e8894e0613"},
{file = "build-1.2.2.tar.gz", hash = "sha256:119b2fb462adef986483438377a13b2f42064a2a3a4161f24a0cca698a07ac8c"},
{file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"},
{file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"},
]
[package.dependencies]
@ -516,21 +516,6 @@ files = [
{file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"},
]
[[package]]
name = "dill"
version = "0.3.8"
description = "serialize all of Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"},
{file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"},
]
[package.extras]
graph = ["objgraph (>=1.7.2)"]
profile = ["gprof2dot (>=2022.7.29)"]
[[package]]
name = "distlib"
version = "0.3.8"
@ -719,13 +704,13 @@ files = [
[[package]]
name = "httpcore"
version = "1.0.5"
version = "1.0.6"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
{file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"},
{file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"},
]
[package.dependencies]
@ -736,7 +721,7 @@ h11 = ">=0.13,<0.15"
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<0.26.0)"]
trio = ["trio (>=0.22.0,<1.0)"]
[[package]]
name = "httpx"
@ -863,21 +848,25 @@ test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-c
[[package]]
name = "jaraco-functools"
version = "4.0.2"
version = "4.1.0"
description = "Functools like those found in stdlib"
optional = false
python-versions = ">=3.8"
files = [
{file = "jaraco.functools-4.0.2-py3-none-any.whl", hash = "sha256:c9d16a3ed4ccb5a889ad8e0b7a343401ee5b2a71cee6ed192d3f68bc351e94e3"},
{file = "jaraco_functools-4.0.2.tar.gz", hash = "sha256:3460c74cd0d32bf82b9576bbb3527c4364d5b27a21f5158a62aed6c4b42e23f5"},
{file = "jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649"},
{file = "jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d"},
]
[package.dependencies]
more-itertools = "*"
[package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
test = ["jaraco.classes", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
enabler = ["pytest-enabler (>=2.2)"]
test = ["jaraco.classes", "pytest (>=6,!=8.1.*)"]
type = ["pytest-mypy"]
[[package]]
name = "jeepney"
@ -1185,64 +1174,64 @@ files = [
[[package]]
name = "numpy"
version = "2.1.1"
version = "2.1.2"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.10"
files = [
{file = "numpy-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8a0e34993b510fc19b9a2ce7f31cb8e94ecf6e924a40c0c9dd4f62d0aac47d9"},
{file = "numpy-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dd86dfaf7c900c0bbdcb8b16e2f6ddf1eb1fe39c6c8cca6e94844ed3152a8fd"},
{file = "numpy-2.1.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:5889dd24f03ca5a5b1e8a90a33b5a0846d8977565e4ae003a63d22ecddf6782f"},
{file = "numpy-2.1.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:59ca673ad11d4b84ceb385290ed0ebe60266e356641428c845b39cd9df6713ab"},
{file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ce49a34c44b6de5241f0b38b07e44c1b2dcacd9e36c30f9c2fcb1bb5135db7"},
{file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913cc1d311060b1d409e609947fa1b9753701dac96e6581b58afc36b7ee35af6"},
{file = "numpy-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:caf5d284ddea7462c32b8d4a6b8af030b6c9fd5332afb70e7414d7fdded4bfd0"},
{file = "numpy-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:57eb525e7c2a8fdee02d731f647146ff54ea8c973364f3b850069ffb42799647"},
{file = "numpy-2.1.1-cp310-cp310-win32.whl", hash = "sha256:9a8e06c7a980869ea67bbf551283bbed2856915f0a792dc32dd0f9dd2fb56728"},
{file = "numpy-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d10c39947a2d351d6d466b4ae83dad4c37cd6c3cdd6d5d0fa797da56f710a6ae"},
{file = "numpy-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d07841fd284718feffe7dd17a63a2e6c78679b2d386d3e82f44f0108c905550"},
{file = "numpy-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5613cfeb1adfe791e8e681128f5f49f22f3fcaa942255a6124d58ca59d9528f"},
{file = "numpy-2.1.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0b8cc2715a84b7c3b161f9ebbd942740aaed913584cae9cdc7f8ad5ad41943d0"},
{file = "numpy-2.1.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b49742cdb85f1f81e4dc1b39dcf328244f4d8d1ded95dea725b316bd2cf18c95"},
{file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d5f8a8e3bc87334f025194c6193e408903d21ebaeb10952264943a985066ca"},
{file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d51fc141ddbe3f919e91a096ec739f49d686df8af254b2053ba21a910ae518bf"},
{file = "numpy-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:98ce7fb5b8063cfdd86596b9c762bf2b5e35a2cdd7e967494ab78a1fa7f8b86e"},
{file = "numpy-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:24c2ad697bd8593887b019817ddd9974a7f429c14a5469d7fad413f28340a6d2"},
{file = "numpy-2.1.1-cp311-cp311-win32.whl", hash = "sha256:397bc5ce62d3fb73f304bec332171535c187e0643e176a6e9421a6e3eacef06d"},
{file = "numpy-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:ae8ce252404cdd4de56dcfce8b11eac3c594a9c16c231d081fb705cf23bd4d9e"},
{file = "numpy-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c803b7934a7f59563db459292e6aa078bb38b7ab1446ca38dd138646a38203e"},
{file = "numpy-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6435c48250c12f001920f0751fe50c0348f5f240852cfddc5e2f97e007544cbe"},
{file = "numpy-2.1.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3269c9eb8745e8d975980b3a7411a98976824e1fdef11f0aacf76147f662b15f"},
{file = "numpy-2.1.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521"},
{file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b"},
{file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b9cd92c8f8e7b313b80e93cedc12c0112088541dcedd9197b5dee3738c1201"},
{file = "numpy-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:afd9c680df4de71cd58582b51e88a61feed4abcc7530bcd3d48483f20fc76f2a"},
{file = "numpy-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8661c94e3aad18e1ea17a11f60f843a4933ccaf1a25a7c6a9182af70610b2313"},
{file = "numpy-2.1.1-cp312-cp312-win32.whl", hash = "sha256:950802d17a33c07cba7fd7c3dcfa7d64705509206be1606f196d179e539111ed"},
{file = "numpy-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:3fc5eabfc720db95d68e6646e88f8b399bfedd235994016351b1d9e062c4b270"},
{file = "numpy-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:046356b19d7ad1890c751b99acad5e82dc4a02232013bd9a9a712fddf8eb60f5"},
{file = "numpy-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e5a9cb2be39350ae6c8f79410744e80154df658d5bea06e06e0ac5bb75480d5"},
{file = "numpy-2.1.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d4c57b68c8ef5e1ebf47238e99bf27657511ec3f071c465f6b1bccbef12d4136"},
{file = "numpy-2.1.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:8ae0fd135e0b157365ac7cc31fff27f07a5572bdfc38f9c2d43b2aff416cc8b0"},
{file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981707f6b31b59c0c24bcda52e5605f9701cb46da4b86c2e8023656ad3e833cb"},
{file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca4b53e1e0b279142113b8c5eb7d7a877e967c306edc34f3b58e9be12fda8df"},
{file = "numpy-2.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e097507396c0be4e547ff15b13dc3866f45f3680f789c1a1301b07dadd3fbc78"},
{file = "numpy-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7506387e191fe8cdb267f912469a3cccc538ab108471291636a96a54e599556"},
{file = "numpy-2.1.1-cp313-cp313-win32.whl", hash = "sha256:251105b7c42abe40e3a689881e1793370cc9724ad50d64b30b358bbb3a97553b"},
{file = "numpy-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:f212d4f46b67ff604d11fff7cc62d36b3e8714edf68e44e9760e19be38c03eb0"},
{file = "numpy-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:920b0911bb2e4414c50e55bd658baeb78281a47feeb064ab40c2b66ecba85553"},
{file = "numpy-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bab7c09454460a487e631ffc0c42057e3d8f2a9ddccd1e60c7bb8ed774992480"},
{file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:cea427d1350f3fd0d2818ce7350095c1a2ee33e30961d2f0fef48576ddbbe90f"},
{file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:e30356d530528a42eeba51420ae8bf6c6c09559051887196599d96ee5f536468"},
{file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8dfa9e94fc127c40979c3eacbae1e61fda4fe71d84869cc129e2721973231ef"},
{file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910b47a6d0635ec1bd53b88f86120a52bf56dcc27b51f18c7b4a2e2224c29f0f"},
{file = "numpy-2.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:13cc11c00000848702322af4de0147ced365c81d66053a67c2e962a485b3717c"},
{file = "numpy-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53e27293b3a2b661c03f79aa51c3987492bd4641ef933e366e0f9f6c9bf257ec"},
{file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7be6a07520b88214ea85d8ac8b7d6d8a1839b0b5cb87412ac9f49fa934eb15d5"},
{file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:52ac2e48f5ad847cd43c4755520a2317f3380213493b9d8a4c5e37f3b87df504"},
{file = "numpy-2.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a95ca3560a6058d6ea91d4629a83a897ee27c00630aed9d933dff191f170cd"},
{file = "numpy-2.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99f4a9ee60eed1385a86e82288971a51e71df052ed0b2900ed30bc840c0f2e39"},
{file = "numpy-2.1.1.tar.gz", hash = "sha256:d0cf7d55b1051387807405b3898efafa862997b4cba8aa5dbe657be794afeafd"},
{file = "numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee"},
{file = "numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884"},
{file = "numpy-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648"},
{file = "numpy-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d"},
{file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86"},
{file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7"},
{file = "numpy-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03"},
{file = "numpy-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466"},
{file = "numpy-2.1.2-cp310-cp310-win32.whl", hash = "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb"},
{file = "numpy-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2"},
{file = "numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe"},
{file = "numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1"},
{file = "numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f"},
{file = "numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4"},
{file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a"},
{file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1"},
{file = "numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2"},
{file = "numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146"},
{file = "numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c"},
{file = "numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9"},
{file = "numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b"},
{file = "numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db"},
{file = "numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1"},
{file = "numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426"},
{file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0"},
{file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df"},
{file = "numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366"},
{file = "numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142"},
{file = "numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550"},
{file = "numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e"},
{file = "numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d"},
{file = "numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf"},
{file = "numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e"},
{file = "numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3"},
{file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8"},
{file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a"},
{file = "numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98"},
{file = "numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe"},
{file = "numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a"},
{file = "numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445"},
{file = "numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5"},
{file = "numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0"},
{file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17"},
{file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6"},
{file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8"},
{file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35"},
{file = "numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62"},
{file = "numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a"},
{file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952"},
{file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5"},
{file = "numpy-2.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7"},
{file = "numpy-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e"},
{file = "numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c"},
]
[[package]]
@ -1564,13 +1553,13 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
version = "3.8.0"
version = "4.0.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
python-versions = ">=3.9"
files = [
{file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"},
{file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"},
{file = "pre_commit-4.0.0-py2.py3-none-any.whl", hash = "sha256:0ca2341cf94ac1865350970951e54b1a50521e57b7b500403307aed4315a1234"},
{file = "pre_commit-4.0.0.tar.gz", hash = "sha256:5d9807162cc5537940f94f266cbe2d716a75cfad0d78a317a92cac16287cfed6"},
]
[package.dependencies]
@ -1788,13 +1777,13 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pyproject-hooks"
version = "1.1.0"
version = "1.2.0"
description = "Wrappers to call pyproject.toml-based build backend hooks."
optional = false
python-versions = ">=3.7"
files = [
{file = "pyproject_hooks-1.1.0-py3-none-any.whl", hash = "sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2"},
{file = "pyproject_hooks-1.1.0.tar.gz", hash = "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965"},
{file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"},
{file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"},
]
[[package]]
@ -1829,13 +1818,13 @@ files = [
[[package]]
name = "pytest"
version = "7.4.4"
version = "8.3.3"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
{file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
{file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
]
[package.dependencies]
@ -1843,29 +1832,29 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
pluggy = ">=1.5,<2"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-asyncio"
version = "0.21.2"
version = "0.24.0"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"},
{file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"},
{file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"},
{file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"},
]
[package.dependencies]
pytest = ">=7.0.0"
pytest = ">=8.2,<9"
[package.extras]
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
[[package]]
name = "pytest-base-url"
@ -1907,13 +1896,13 @@ histogram = ["pygal", "pygaljs"]
[[package]]
name = "pytest-cov"
version = "4.1.0"
version = "5.0.0"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
{file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
{file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"},
{file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
]
[package.dependencies]
@ -1921,7 +1910,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]}
pytest = ">=4.6"
[package.extras]
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
[[package]]
name = "pytest-mock"
@ -1992,13 +1981,13 @@ docs = ["sphinx"]
[[package]]
name = "python-multipart"
version = "0.0.10"
version = "0.0.12"
description = "A streaming multipart parser for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "python_multipart-0.0.10-py3-none-any.whl", hash = "sha256:2b06ad9e8d50c7a8db80e3b56dab590137b323410605af2be20d62a5f1ba1dc8"},
{file = "python_multipart-0.0.10.tar.gz", hash = "sha256:46eb3c6ce6fdda5fb1a03c7e11d490e407c6930a2703fe7aef4da71c374688fa"},
{file = "python_multipart-0.0.12-py3-none-any.whl", hash = "sha256:43dcf96cf65888a9cd3423544dd0d75ac10f7aa0c3c28a175bbcd00c9ce1aebf"},
{file = "python_multipart-0.0.12.tar.gz", hash = "sha256:045e1f98d719c1ce085ed7f7e1ef9d8ccc8c02ba02b5566d5f7521410ced58cb"},
]
[[package]]
@ -2143,31 +2132,31 @@ md = ["cmarkgfm (>=0.8.0)"]
[[package]]
name = "redis"
version = "5.0.8"
version = "5.1.1"
description = "Python client for Redis database and key-value store"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "redis-5.0.8-py3-none-any.whl", hash = "sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4"},
{file = "redis-5.0.8.tar.gz", hash = "sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870"},
{file = "redis-5.1.1-py3-none-any.whl", hash = "sha256:f8ea06b7482a668c6475ae202ed8d9bcaa409f6e87fb77ed1043d912afd62e24"},
{file = "redis-5.1.1.tar.gz", hash = "sha256:f6c997521fedbae53387307c5d0bf784d9acc28d9f1d058abeac566ec4dbed72"},
]
[package.dependencies]
async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""}
[package.extras]
hiredis = ["hiredis (>1.0.0)"]
ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"]
hiredis = ["hiredis (>=3.0.0)"]
ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"]
[[package]]
name = "reflex-chakra"
version = "0.6.0"
version = "0.6.1"
description = "reflex using chakra components"
optional = false
python-versions = "<4.0,>=3.8"
files = [
{file = "reflex_chakra-0.6.0-py3-none-any.whl", hash = "sha256:eca1593fca67289e05591dd21fbcc8632c119d64a08bdc41fd995055a114cc91"},
{file = "reflex_chakra-0.6.0.tar.gz", hash = "sha256:db1c7b48f1ba547bf91e5af103fce6fc7191d7225b414ebfbada7d983e33dd87"},
{file = "reflex_chakra-0.6.1-py3-none-any.whl", hash = "sha256:824d461264b6d2c836ba4a2a430e677a890b82e83da149672accfc58786442fa"},
{file = "reflex_chakra-0.6.1.tar.gz", hash = "sha256:4b9b3c8bada19cbb4d1b8d8bc4ab0460ec008a91f380010c34d416d5b613dc07"},
]
[package.dependencies]
@ -2247,46 +2236,48 @@ idna2008 = ["idna"]
[[package]]
name = "rich"
version = "13.8.1"
version = "13.9.2"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.7.0"
python-versions = ">=3.8.0"
files = [
{file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"},
{file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"},
{file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"},
{file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"},
]
[package.dependencies]
markdown-it-py = ">=2.2.0"
pygments = ">=2.13.0,<3.0.0"
typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""}
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
version = "0.4.10"
version = "0.6.9"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"},
{file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"},
{file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"},
{file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"},
{file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"},
{file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"},
{file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"},
{file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"},
{file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"},
{file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"},
{file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"},
{file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"},
{file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"},
{file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"},
{file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"},
{file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"},
{file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"},
{file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"},
{file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"},
{file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"},
{file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"},
]
[[package]]
@ -2325,18 +2316,23 @@ websocket-client = ">=1.8,<2.0"
[[package]]
name = "setuptools"
version = "70.1.1"
version = "75.1.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-70.1.1-py3-none-any.whl", hash = "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95"},
{file = "setuptools-70.1.1.tar.gz", hash = "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650"},
{file = "setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2"},
{file = "setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"]
core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
enabler = ["pytest-enabler (>=2.2)"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"]
[[package]]
name = "shellingham"
@ -2595,13 +2591,13 @@ files = [
[[package]]
name = "tomli"
version = "2.0.1"
version = "2.0.2"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
{file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"},
{file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"},
]
[[package]]
@ -2734,13 +2730,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "uvicorn"
version = "0.30.6"
version = "0.31.0"
description = "The lightning-fast ASGI server."
optional = false
python-versions = ">=3.8"
files = [
{file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"},
{file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788"},
{file = "uvicorn-0.31.0-py3-none-any.whl", hash = "sha256:cac7be4dd4d891c363cd942160a7b02e69150dcbc7a36be04d5f4af4b17c8ced"},
{file = "uvicorn-0.31.0.tar.gz", hash = "sha256:13bc21373d103859f68fe739608e2eb054a816dea79189bc3ca08ea89a275906"},
]
[package.dependencies]
@ -2753,13 +2749,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
[[package]]
name = "virtualenv"
version = "20.26.5"
version = "20.26.6"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
files = [
{file = "virtualenv-20.26.5-py3-none-any.whl", hash = "sha256:4f3ac17b81fba3ce3bd6f4ead2749a72da5929c01774948e243db9ba41df4ff6"},
{file = "virtualenv-20.26.5.tar.gz", hash = "sha256:ce489cac131aa58f4b25e321d6d186171f78e6cb13fafbf32a840cee67733ff4"},
{file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"},
{file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"},
]
[package.dependencies]
@ -3011,4 +3007,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = "^3.9"
content-hash = "adccd071775567aeefe219261aeb9e222906c865745f03edb1e770edc79c44ac"
content-hash = "796ddba36f031ad2b47cae43ce6c49102e2cb98f92823b265d9779e6684333f6"

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "reflex"
version = "0.6.2dev1"
version = "0.6.3dev1"
description = "Web apps in pure Python."
license = "Apache-2.0"
authors = [
@ -27,7 +27,6 @@ packages = [
[tool.poetry.dependencies]
python = "^3.9"
dill = ">=0.3.8,<0.4"
fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
gunicorn = ">=20.1.0,<24.0"
jinja2 = ">=3.1.2,<4.0"
@ -54,7 +53,7 @@ reflex-hosting-cli = ">=0.1.2,<2.0"
charset-normalizer = ">=3.3.2,<4.0"
wheel = ">=0.42.0,<1.0"
build = ">=1.0.3,<2.0"
setuptools = ">=69.1.1,<70.2"
setuptools = ">=75.0"
httpx = ">=0.25.1,<1.0"
twine = ">=4.0.0,<6.0"
tomlkit = ">=0.12.4,<1.0"
@ -62,14 +61,14 @@ lazy_loader = ">=0.4"
reflex-chakra = ">=0.6.0"
[tool.poetry.group.dev.dependencies]
pytest = ">=7.1.2,<8.0"
pytest = ">=7.1.2,<9.0"
pytest-mock = ">=3.10.0,<4.0"
pyright = ">=1.1.229,<1.1.335"
darglint = ">=1.8.1,<2.0"
toml = ">=0.10.2,<1.0"
pytest-asyncio = ">=0.20.1,<0.22.0" # https://github.com/pytest-dev/pytest-asyncio/issues/706
pytest-cov = ">=4.0.0,<5.0"
ruff = "^0.4.9"
pytest-asyncio = ">=0.24.0"
pytest-cov = ">=4.0.0,<6.0"
ruff = "^0.6.9"
pandas = ">=2.1.1,<3.0"
pillow = ">=10.0.0,<11.0"
plotly = ">=5.13.0,<6.0"
@ -101,3 +100,7 @@ lint.pydocstyle.convention = "google"
"reflex/.templates/*.py" = ["D100", "D103", "D104"]
"*.pyi" = ["D301", "D415", "D417", "D418", "E742"]
"*/blank.py" = ["I001"]
[tool.pytest.ini_options]
asyncio_default_fixture_loop_scope = "function"
asyncio_mode = "auto"

View File

@ -7,10 +7,9 @@ import '/styles/styles.css'
{% block declaration %}
import { EventLoopProvider, StateProvider, defaultColorMode } from "/utils/context.js";
import { ThemeProvider } from 'next-themes'
import * as React from "react";
import * as utils_context from "/utils/context.js";
import * as utils_state from "/utils/state.js";
import * as radix from "@radix-ui/themes";
{% for library_alias, library_path in window_libraries %}
import * as {{library_alias}} from "{{library_path}}";
{% endfor %}
{% for custom_code in custom_codes %}
{{custom_code}}
@ -33,10 +32,9 @@ export default function MyApp({ Component, pageProps }) {
React.useEffect(() => {
// Make contexts and state objects available globally for dynamic eval'd components
let windowImports = {
"react": React,
"@radix-ui/themes": radix,
"/utils/context": utils_context,
"/utils/state": utils_state,
{% for library_alias, library_path in window_libraries %}
"{{library_path}}": {{library_alias}},
{% endfor %}
};
window["__reflex"] = windowImports;
}, []);

View File

@ -544,13 +544,19 @@ export const uploadFiles = async (
/**
* Create an event object.
* @param name The name of the event.
* @param payload The payload of the event.
* @param handler The client handler to process event.
* @param {string} name The name of the event.
* @param {Object.<string, Any>} payload The payload of the event.
* @param {Object.<string, (number|boolean)>} event_actions The actions to take on the event.
* @param {string} handler The client handler to process event.
* @returns The event object.
*/
export const Event = (name, payload = {}, handler = null) => {
return { name, payload, handler };
export const Event = (
name,
payload = {},
event_actions = {},
handler = null
) => {
return { name, payload, handler, event_actions };
};
/**
@ -676,6 +682,12 @@ export const useEventLoop = (
if (!(args instanceof Array)) {
args = [args];
}
event_actions = events.reduce(
(acc, e) => ({ ...acc, ...e.event_actions }),
event_actions ?? {}
);
const _e = args.filter((o) => o?.preventDefault !== undefined)[0];
if (event_actions?.preventDefault && _e?.preventDefault) {

View File

@ -331,7 +331,7 @@ _MAPPING: dict = {
"style": ["Style", "toggle_color_mode"],
"utils.imports": ["ImportVar"],
"utils.serializers": ["serializer"],
"vars": ["Var"],
"vars": ["Var", "field", "Field"],
}
_SUBMODULES: set[str] = {

View File

@ -189,7 +189,9 @@ from .style import Style as Style
from .style import toggle_color_mode as toggle_color_mode
from .utils.imports import ImportVar as ImportVar
from .utils.serializers import serializer as serializer
from .vars import Field as Field
from .vars import Var as Var
from .vars import field as field
del compat
RADIX_THEMES_MAPPING: dict

View File

@ -431,25 +431,12 @@ class App(MiddlewareMixin, LifespanMixin, Base):
The generated component.
Raises:
VarOperationTypeError: When an invalid component var related function is passed.
TypeError: When an invalid component function is passed.
exceptions.MatchTypeError: If the return types of match cases in rx.match are different.
"""
from reflex.utils.exceptions import VarOperationTypeError
try:
return component if isinstance(component, Component) else component()
except exceptions.MatchTypeError:
raise
except TypeError as e:
message = str(e)
if "Var" in message:
raise VarOperationTypeError(
"You may be trying to use an invalid Python function on a state var. "
"When referencing a var inside your render code, only limited var operations are supported. "
"See the var operation docs here: https://reflex.dev/docs/vars/var-operations/"
) from e
raise e
def add_page(
self,
@ -1536,7 +1523,9 @@ class EventNamespace(AsyncNamespace):
"""
fields = json.loads(data)
# Get the event.
event = Event(**{k: v for k, v in fields.items() if k != "handler"})
event = Event(
**{k: v for k, v in fields.items() if k not in ("handler", "event_actions")}
)
self.token_to_sid[event.token] = sid
self.sid_to_token[sid] = event.token

View File

@ -40,6 +40,20 @@ def _compile_document_root(root: Component) -> str:
)
def _normalize_library_name(lib: str) -> str:
"""Normalize the library name.
Args:
lib: The library name to normalize.
Returns:
The normalized library name.
"""
if lib == "react":
return "React"
return lib.replace("@", "").replace("/", "_").replace("-", "_")
def _compile_app(app_root: Component) -> str:
"""Compile the app template component.
@ -49,10 +63,20 @@ def _compile_app(app_root: Component) -> str:
Returns:
The compiled app.
"""
from reflex.components.dynamic import bundled_libraries
window_libraries = [
(_normalize_library_name(name), name) for name in bundled_libraries
] + [
("utils_context", f"/{constants.Dirs.UTILS}/context"),
("utils_state", f"/{constants.Dirs.UTILS}/state"),
]
return templates.APP_ROOT.render(
imports=utils.compile_imports(app_root._get_all_imports()),
custom_codes=app_root._get_all_custom_code(),
hooks={**app_root._get_all_hooks_internal(), **app_root._get_all_hooks()},
window_libraries=window_libraries,
render=app_root.render(),
)
@ -171,7 +195,7 @@ def _compile_root_stylesheet(stylesheets: list[str]) -> str:
stylesheet_full_path = (
Path.cwd() / constants.Dirs.APP_ASSETS / stylesheet.strip("/")
)
if not os.path.exists(stylesheet_full_path):
if not stylesheet_full_path.exists():
raise FileNotFoundError(
f"The stylesheet file {stylesheet_full_path} does not exist."
)

View File

@ -2,7 +2,6 @@
from __future__ import annotations
import os
from pathlib import Path
from typing import Any, Callable, Dict, Optional, Type, Union
from urllib.parse import urlparse
@ -457,16 +456,16 @@ def add_meta(
return page
def write_page(path: str, code: str):
def write_page(path: str | Path, code: str):
"""Write the given code to the given path.
Args:
path: The path to write the code to.
code: The code to write.
"""
path_ops.mkdir(os.path.dirname(path))
with open(path, "w", encoding="utf-8") as f:
f.write(code)
path = Path(path)
path_ops.mkdir(path.parent)
path.write_text(code, encoding="utf-8")
def empty_dir(path: str | Path, keep_files: list[str] | None = None):

View File

@ -38,8 +38,10 @@ from reflex.constants import (
)
from reflex.event import (
EventChain,
EventChainVar,
EventHandler,
EventSpec,
EventVar,
call_event_fn,
call_event_handler,
get_handler_args,
@ -514,7 +516,7 @@ class Component(BaseComponent, ABC):
Var,
EventHandler,
EventSpec,
List[Union[EventHandler, EventSpec]],
List[Union[EventHandler, EventSpec, EventVar]],
Callable,
],
) -> Union[EventChain, Var]:
@ -532,11 +534,16 @@ class Component(BaseComponent, ABC):
"""
# If it's an event chain var, return it.
if isinstance(value, Var):
if value._var_type is not EventChain:
if isinstance(value, EventChainVar):
return value
elif isinstance(value, EventVar):
value = [value]
elif issubclass(value._var_type, (EventChain, EventSpec)):
return self._create_event_chain(args_spec, value.guess_type())
else:
raise ValueError(
f"Invalid event chain: {repr(value)} of type {type(value)}"
f"Invalid event chain: {str(value)} of type {value._var_type}"
)
return value
elif isinstance(value, EventChain):
# Trust that the caller knows what they're doing passing an EventChain directly
return value
@ -547,7 +554,7 @@ class Component(BaseComponent, ABC):
# If the input is a list of event handlers, create an event chain.
if isinstance(value, List):
events: list[EventSpec] = []
events: List[Union[EventSpec, EventVar]] = []
for v in value:
if isinstance(v, (EventHandler, EventSpec)):
# Call the event handler to get the event.
@ -561,6 +568,8 @@ class Component(BaseComponent, ABC):
"lambda inside an EventChain list."
)
events.extend(result)
elif isinstance(v, EventVar):
events.append(v)
else:
raise ValueError(f"Invalid event: {v}")
@ -570,32 +579,30 @@ class Component(BaseComponent, ABC):
if isinstance(result, Var):
# Recursively call this function if the lambda returned an EventChain Var.
return self._create_event_chain(args_spec, result)
events = result
events = [*result]
# Otherwise, raise an error.
else:
raise ValueError(f"Invalid event chain: {value}")
# Add args to the event specs if necessary.
events = [e.with_args(get_handler_args(e)) for e in events]
# Collect event_actions from each spec
event_actions = {}
for e in events:
event_actions.update(e.event_actions)
events = [
(e.with_args(get_handler_args(e)) if isinstance(e, EventSpec) else e)
for e in events
]
# Return the event chain.
if isinstance(args_spec, Var):
return EventChain(
events=events,
args_spec=None,
event_actions=event_actions,
event_actions={},
)
else:
return EventChain(
events=events,
args_spec=args_spec,
event_actions=event_actions,
event_actions={},
)
def get_event_triggers(self) -> Dict[str, Any]:
@ -1030,8 +1037,11 @@ class Component(BaseComponent, ABC):
elif isinstance(event, EventChain):
event_args = []
for spec in event.events:
for args in spec.args:
event_args.extend(args)
if isinstance(spec, EventSpec):
for args in spec.args:
event_args.extend(args)
else:
event_args.append(spec)
yield event_trigger, event_args
def _get_vars(self, include_children: bool = False) -> list[Var]:
@ -1105,8 +1115,12 @@ class Component(BaseComponent, ABC):
for trigger in self.event_triggers.values():
if isinstance(trigger, EventChain):
for event in trigger.events:
if event.handler.state_full_name:
return True
if isinstance(event, EventSpec):
if event.handler.state_full_name:
return True
else:
if event._var_state:
return True
elif isinstance(trigger, Var) and trigger._var_state:
return True
return False

View File

@ -179,7 +179,7 @@ class UploadFilesProvider(Component):
class Upload(MemoizationLeaf):
"""A file upload component."""
library = "react-dropzone@14.2.3"
library = "react-dropzone@14.2.9"
tag = "ReactDropzone"

View File

@ -125,10 +125,10 @@ class DataEditor(NoSSRComponent):
tag = "DataEditor"
is_default = True
library: str = "@glideapps/glide-data-grid@^5.3.0"
library: str = "@glideapps/glide-data-grid@^6.0.3"
lib_dependencies: List[str] = [
"lodash@^4.17.21",
"marked@^4.0.10",
"marked@^14.1.2",
"react-responsive-carousel@^3.2.7",
]
@ -329,7 +329,7 @@ class DataEditor(NoSSRComponent):
columns = props.get("columns", [])
data = props.get("data", [])
rows = props.get("rows", None)
rows = props.get("rows")
# If rows is not provided, determine from data.
if rows is None:

View File

@ -1,12 +1,18 @@
"""Components that are dynamically generated on the backend."""
from typing import TYPE_CHECKING, Union
from reflex import constants
from reflex.utils import imports
from reflex.utils.exceptions import DynamicComponentMissingLibrary
from reflex.utils.format import format_library_name
from reflex.utils.serializers import serializer
from reflex.vars import Var, get_unique_variable_name
from reflex.vars.base import VarData, transform
if TYPE_CHECKING:
from reflex.components.component import Component
def get_cdn_url(lib: str) -> str:
"""Get the CDN URL for a library.
@ -20,6 +26,26 @@ def get_cdn_url(lib: str) -> str:
return f"https://cdn.jsdelivr.net/npm/{lib}" + "/+esm"
bundled_libraries = {"react", "@radix-ui/themes", "@emotion/react", "next/link"}
def bundle_library(component: Union["Component", str]):
"""Bundle a library with the component.
Args:
component: The component to bundle the library with.
Raises:
DynamicComponentMissingLibrary: Raised when a dynamic component is missing a library.
"""
if isinstance(component, str):
bundled_libraries.add(component)
return
if component.library is None:
raise DynamicComponentMissingLibrary("Component must have a library to bundle.")
bundled_libraries.add(format_library_name(component.library))
def load_dynamic_serializer():
"""Load the serializer for dynamic components."""
# Causes a circular import, so we import here.
@ -58,10 +84,7 @@ def load_dynamic_serializer():
)
] = None
libs_in_window = [
"react",
"@radix-ui/themes",
]
libs_in_window = bundled_libraries
imports = {}
for lib, names in component._get_all_imports().items():
@ -69,10 +92,7 @@ def load_dynamic_serializer():
if (
not lib.startswith((".", "/"))
and not lib.startswith("http")
and all(
formatted_lib_name != lib_in_window
for lib_in_window in libs_in_window
)
and formatted_lib_name not in libs_in_window
):
imports[get_cdn_url(lib)] = names
else:
@ -110,7 +130,14 @@ def load_dynamic_serializer():
module_code_lines.insert(0, "const React = window.__reflex.react;")
return "//__reflex_evaluate\n" + "\n".join(module_code_lines)
return "\n".join(
[
"//__reflex_evaluate",
"/** @jsx jsx */",
"const { jsx } = window.__reflex['@emotion/react']",
*module_code_lines,
]
)
@transform
def evaluate_component(js_string: Var[str]) -> Var[Component]:

View File

@ -3,6 +3,7 @@
# This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------
from . import elements as elements
from .elements.forms import Button as Button
from .elements.forms import Fieldset as Fieldset
from .elements.forms import Form as Form

View File

@ -15,9 +15,9 @@ from reflex.vars.base import LiteralVar, Var, is_computed_var
class Gridjs(Component):
"""A component that wraps a nivo bar component."""
library = "gridjs-react@6.0.1"
library = "gridjs-react@6.1.1"
lib_dependencies: List[str] = ["gridjs@6.0.6"]
lib_dependencies: List[str] = ["gridjs@6.2.0"]
class DataTable(Gridjs):

View File

@ -94,7 +94,7 @@ class Plotly(NoSSRComponent):
library = "react-plotly.js@2.6.0"
lib_dependencies: List[str] = ["plotly.js@2.22.0"]
lib_dependencies: List[str] = ["plotly.js@2.35.2"]
tag = "Plot"

View File

@ -17,7 +17,7 @@ from .base import RadixPrimitiveComponentWithClassName
class FormComponent(RadixPrimitiveComponentWithClassName):
"""Base class for all @radix-ui/react-form components."""
library = "@radix-ui/react-form@^0.0.3"
library = "@radix-ui/react-form@^0.1.0"
class FormRoot(FormComponent, HTMLForm):

View File

@ -12,7 +12,7 @@ class ReactPlayer(NoSSRComponent):
reference: https://github.com/cookpete/react-player.
"""
library = "react-player@2.12.0"
library = "react-player@2.16.0"
tag = "ReactPlayer"

View File

@ -129,20 +129,20 @@ class XAxis(Axis):
alias = "RechartsXAxis"
# The orientation of axis 'top' | 'bottom'
# The orientation of axis 'top' | 'bottom'. Default: "bottom"
orientation: Var[LiteralOrientationTopBottom]
# The id of x-axis which is corresponding to the data.
# The id of x-axis which is corresponding to the data. Default: 0
x_axis_id: Var[Union[str, int]]
# Ensures that all datapoints within a chart contribute to its domain calculation, even when they are hidden
include_hidden: Var[bool] = LiteralVar.create(False)
# Ensures that all datapoints within a chart contribute to its domain calculation, even when they are hidden. Default: False
include_hidden: Var[bool]
# The range of the axis. Work best in conjuction with allow_data_overflow.
domain: Var[List]
# The angle of axis ticks. Default: 0
angle: Var[int]
# The range of the axis. Work best in conjuction with allow_data_overflow.
domain: Var[List]
# Specify the padding of x-axis. Default: {"left": 0, "right": 0}
padding: Var[Dict[str, int]]
class YAxis(Axis):
@ -152,14 +152,14 @@ class YAxis(Axis):
alias = "RechartsYAxis"
# The orientation of axis 'left' | 'right'
# The orientation of axis 'left' | 'right'. Default: "left"
orientation: Var[LiteralOrientationLeftRight]
# The id of y-axis which is corresponding to the data.
# The id of y-axis which is corresponding to the data. Default: 0
y_axis_id: Var[Union[str, int]]
# The range of the axis. Work best in conjuction with allow_data_overflow.
domain: Var[List]
# Specify the padding of y-axis. Default: {"top": 0, "bottom": 0}
padding: Var[Dict[str, int]]
class ZAxis(Recharts):
@ -172,7 +172,10 @@ class ZAxis(Recharts):
# The key of data displayed in the axis.
data_key: Var[Union[str, int]]
# The range of axis.
# The unique id of z-axis. Default: 0
z_axis_id: Var[Union[str, int]]
# The range of axis. Default: [10, 10]
range: Var[List[int]]
# The unit of data displayed in the axis. This option will be used to represent an index unit in a scatter chart.
@ -181,7 +184,7 @@ class ZAxis(Recharts):
# The name of data displayed in the axis. This option will be used to represent an index in a scatter chart.
name: Var[Union[str, int]]
# If 'auto' set, the scale function is decided by the type of chart, and the props type.
# If 'auto' set, the scale function is decided by the type of chart, and the props type. Default: "auto"
scale: Var[LiteralScale]
@ -192,40 +195,40 @@ class Brush(Recharts):
alias = "RechartsBrush"
# Stroke color
# Stroke color. Default: rx.color("gray", 9)
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 9))
# The fill color of brush.
# The fill color of brush. Default: rx.color("gray", 2)
fill: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 2))
# The key of data displayed in the axis.
data_key: Var[Union[str, int]]
# The x-coordinate of brush.
# The x-coordinate of brush. Default: 0
x: Var[int]
# The y-coordinate of brush.
# The y-coordinate of brush. Default: 0
y: Var[int]
# The width of brush.
# The width of brush. Default: 0
width: Var[int]
# The height of brush.
# The height of brush. Default: 40
height: Var[int]
# The data domain of brush, [min, max].
# The original data of a LineChart, a BarChart or an AreaChart.
data: Var[List[Any]]
# The width of each traveller.
# The width of each traveller. Default: 5
traveller_width: Var[int]
# The data with gap of refreshing chart. If the option is not set, the chart will be refreshed every time
# The data with gap of refreshing chart. If the option is not set, the chart will be refreshed every time. Default: 1
gap: Var[int]
# The default start index of brush. If the option is not set, the start index will be 0.
# The default start index of brush. If the option is not set, the start index will be 0. Default: 0
start_index: Var[int]
# The default end index of brush. If the option is not set, the end index will be 1.
# The default end index of brush. If the option is not set, the end index will be calculated by the length of data.
end_index: Var[int]
# The fill color of brush
@ -295,22 +298,22 @@ class Area(Cartesian):
alias = "RechartsArea"
# The color of the line stroke.
# The color of the line stroke. Default: rx.color("accent", 9)
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
# The width of the line stroke.
stroke_width: Var[int] = LiteralVar.create(1)
# The width of the line stroke. Default: 1
stroke_width: Var[int]
# The color of the area fill.
# The color of the area fill. Default: rx.color("accent", 5)
fill: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 5))
# The interpolation type of area. And customized interpolation function can be set to type. 'basis' | 'basisClosed' | 'basisOpen' | 'bumpX' | 'bumpY' | 'bump' | 'linear' | 'linearClosed' | 'natural' | 'monotoneX' | 'monotoneY' | 'monotone' | 'step' | 'stepBefore' | 'stepAfter' |
# The interpolation type of area. And customized interpolation function can be set to type. 'basis' | 'basisClosed' | 'basisOpen' | 'bumpX' | 'bumpY' | 'bump' | 'linear' | 'linearClosed' | 'natural' | 'monotoneX' | 'monotoneY' | 'monotone' | 'step' | 'stepBefore' | 'stepAfter'. Default: "monotone"
type_: Var[LiteralAreaType] = LiteralVar.create("monotone")
# If false set, dots will not be drawn. If true set, dots will be drawn which have the props calculated internally.
# If false set, dots will not be drawn. If true set, dots will be drawn which have the props calculated internally. Default: False
dot: Var[Union[bool, Dict[str, Any]]]
# The dot is shown when user enter an area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally.
# The dot is shown when user enter an area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally. Default: {stroke: rx.color("accent", 2), fill: rx.color("accent", 10)}
active_dot: Var[Union[bool, Dict[str, Any]]] = LiteralVar.create(
{
"stroke": Color("accent", 2),
@ -318,17 +321,20 @@ class Area(Cartesian):
}
)
# If set false, labels will not be drawn. If set true, labels will be drawn which have the props calculated internally.
# If set false, labels will not be drawn. If set true, labels will be drawn which have the props calculated internally. Default: False
label: Var[bool]
# The value which can describle the line, usually calculated internally.
base_line: Var[Union[str, List[Dict[str, Any]]]]
# The coordinates of all the points in the area, usually calculated internally.
points: Var[List[Dict[str, Any]]]
# The stack id of area, when two areas have the same value axis and same stack_id, then the two areas are stacked in order.
stack_id: Var[Union[str, int]]
# The unit of data. This option will be used in tooltip.
unit: Var[Union[str, int]]
# The name of data. This option will be used in tooltip and legend to represent a bar. If no value was set to this option, the value of dataKey will be used alternatively.
name: Var[Union[str, int]]
# Whether to connect a graph area across null points. Default: False
connect_nulls: Var[bool]
# Valid children components
_valid_children: List[str] = ["LabelList"]
@ -347,12 +353,13 @@ class Bar(Cartesian):
# The width of the line stroke.
stroke_width: Var[int]
# The width of the line stroke.
# The width of the line stroke. Default: Color("accent", 9)
fill: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
# If false set, background of bars will not be drawn. If true set, background of bars will be drawn which have the props calculated internally.
# If false set, background of bars will not be drawn. If true set, background of bars will be drawn which have the props calculated internally. Default: False
background: Var[bool]
# If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally.
# If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally. Default: False
label: Var[bool]
# The stack id of bar, when two bars have the same value axis and same stack_id, then the two bars are stacked in order.
@ -373,30 +380,15 @@ class Bar(Cartesian):
# Max size of the bar
max_bar_size: Var[int]
# If set a value, the option is the radius of all the rounded corners. If set a array, the option are in turn the radiuses of top-left corner, top-right corner, bottom-right corner, bottom-left corner. Default: 0
radius: Var[Union[int, List[int]]]
# The active bar is shown when a user enters a bar chart and this chart has tooltip. If set to false, no active bar will be drawn. If set to true, active bar will be drawn with the props calculated internally. If passed an object, active bar will be drawn, and the internally calculated props will be merged with the key value pairs of the passed object.
# active_bar: Var[Union[bool, Dict[str, Any]]]
# Valid children components
_valid_children: List[str] = ["Cell", "LabelList", "ErrorBar"]
# If set false, animation of bar will be disabled.
is_animation_active: Var[bool]
# Specifies when the animation should begin, the unit of this option is ms, default 0.
animation_begin: Var[int]
# Specifies the duration of animation, the unit of this option is ms, default 1500.
animation_duration: Var[int]
# The type of easing function, default 'ease'
animation_easing: Var[LiteralAnimationEasing]
# The customized event handler of animation start
on_animation_start: EventHandler[lambda: []]
# The customized event handler of animation end
on_animation_end: EventHandler[lambda: []]
class Line(Cartesian):
"""A Line component in Recharts."""
@ -408,13 +400,13 @@ class Line(Cartesian):
# The interpolation type of line. And customized interpolation function can be set to type. It's the same as type in Area.
type_: Var[LiteralAreaType]
# The color of the line stroke.
# The color of the line stroke. Default: rx.color("accent", 9)
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
# The width of the line stroke.
# The width of the line stroke. Default: 1
stroke_width: Var[int]
# The dot is shown when mouse enter a line chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally.
# The dot is shown when mouse enter a line chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally. Default: {"stroke": rx.color("accent", 10), "fill": rx.color("accent", 4)}
dot: Var[Union[bool, Dict[str, Any]]] = LiteralVar.create(
{
"stroke": Color("accent", 10),
@ -422,7 +414,7 @@ class Line(Cartesian):
}
)
# The dot is shown when user enter an area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally.
# The dot is shown when user enter an area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally. Default: {"stroke": rx.color("accent", 2), "fill": rx.color("accent", 10)}
active_dot: Var[Union[bool, Dict[str, Any]]] = LiteralVar.create(
{
"stroke": Color("accent", 2),
@ -430,10 +422,10 @@ class Line(Cartesian):
}
)
# If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally.
# If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally. Default: False
label: Var[bool]
# Hides the line when true, useful when toggling visibility state via legend.
# Hides the line when true, useful when toggling visibility state via legend. Default: False
hide: Var[bool]
# Whether to connect a graph line across null points.
@ -442,8 +434,11 @@ class Line(Cartesian):
# The unit of data. This option will be used in tooltip.
unit: Var[Union[str, int]]
# The name of data displayed in the axis. This option will be used to represent an index in a scatter chart.
name: Var[Union[str, int]]
# The coordinates of all the points in the line, usually calculated internally.
points: Var[List[Dict[str, Any]]]
# The pattern of dashes and gaps used to paint the line.
stroke_dasharray: Var[str]
# Valid children components
_valid_children: List[str] = ["LabelList", "ErrorBar"]
@ -459,46 +454,43 @@ class Scatter(Recharts):
# The source data, in which each element is an object.
data: Var[List[Dict[str, Any]]]
# The type of icon in legend. If set to 'none', no legend item will be rendered. 'line' | 'plainline' | 'square' | 'rect'| 'circle' | 'cross' | 'diamond' | 'square' | 'star' | 'triangle' | 'wye' | 'none'
# The type of icon in legend. If set to 'none', no legend item will be rendered. 'line' | 'plainline' | 'square' | 'rect'| 'circle' | 'cross' | 'diamond' | 'square' | 'star' | 'triangle' | 'wye' | 'none'. Default: "circle"
legend_type: Var[LiteralLegendType]
# The id of x-axis which is corresponding to the data.
# The id of x-axis which is corresponding to the data. Default: 0
x_axis_id: Var[Union[str, int]]
# The id of y-axis which is corresponding to the data.
# The id of y-axis which is corresponding to the data. Default: 0
y_axis_id: Var[Union[str, int]]
# The id of z-axis which is corresponding to the data.
z_axis_id: Var[str]
# The id of z-axis which is corresponding to the data. Default: 0
z_axis_id: Var[Union[str, int]]
# If false set, line will not be drawn. If true set, line will be drawn which have the props calculated internally.
# If false set, line will not be drawn. If true set, line will be drawn which have the props calculated internally. Default: False
line: Var[bool]
# If a string set, specified symbol will be used to show scatter item. 'circle' | 'cross' | 'diamond' | 'square' | 'star' | 'triangle' | 'wye'
# If a string set, specified symbol will be used to show scatter item. 'circle' | 'cross' | 'diamond' | 'square' | 'star' | 'triangle' | 'wye'. Default: "circle"
shape: Var[LiteralShape]
# If 'joint' set, line will generated by just jointing all the points. If 'fitting' set, line will be generated by fitting algorithm. 'joint' | 'fitting'
# If 'joint' set, line will generated by just jointing all the points. If 'fitting' set, line will be generated by fitting algorithm. 'joint' | 'fitting'. Default: "joint"
line_type: Var[LiteralLineType]
# The fill
# The fill color of the scatter. Default: rx.color("accent", 9)
fill: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
# the name
name: Var[Union[str, int]]
# Valid children components.
_valid_children: List[str] = ["LabelList", "ErrorBar"]
# If set false, animation of bar will be disabled.
# If set false, animation of bar will be disabled. Default: True in CSR, False in SSR
is_animation_active: Var[bool]
# Specifies when the animation should begin, the unit of this option is ms, default 0.
# Specifies when the animation should begin, the unit of this option is ms. Default: 0
animation_begin: Var[int]
# Specifies the duration of animation, the unit of this option is ms, default 1500.
# Specifies the duration of animation, the unit of this option is ms. Default: 1500
animation_duration: Var[int]
# The type of easing function, default 'ease'
# The type of easing function. Default: "ease"
animation_easing: Var[LiteralAnimationEasing]
# The customized event handler of click on the component in this group
@ -620,19 +612,19 @@ class ErrorBar(Recharts):
class Reference(Recharts):
"""A base class for reference components in Reference."""
# The id of x-axis which is corresponding to the data.
# The id of x-axis which is corresponding to the data. Default: 0
x_axis_id: Var[Union[str, int]]
# The id of y-axis which is corresponding to the data.
# The id of y-axis which is corresponding to the data. Default: 0
y_axis_id: Var[Union[str, int]]
# Defines how to draw the reference line if it falls partly outside the canvas. If set to 'discard', the reference line will not be drawn at all. If set to 'hidden', the reference line will be clipped to the canvas. If set to 'visible', the reference line will be drawn completely. If set to 'extendDomain', the domain of the overflown axis will be extended such that the reference line fits into the canvas.
# Defines how to draw the reference line if it falls partly outside the canvas. If set to 'discard', the reference line will not be drawn at all. If set to 'hidden', the reference line will be clipped to the canvas. If set to 'visible', the reference line will be drawn completely. If set to 'extendDomain', the domain of the overflown axis will be extended such that the reference line fits into the canvas. Default: "discard"
if_overflow: Var[LiteralIfOverflow]
# If set a string or a number, default label will be drawn, and the option is content.
label: Var[Union[str, int]]
# If set true, the line will be rendered in front of bars in BarChart, etc.
# If set true, the line will be rendered in front of bars in BarChart, etc. Default: False
is_front: Var[bool]
@ -652,7 +644,7 @@ class ReferenceLine(Reference):
# The color of the reference line.
stroke: Var[Union[str, Color]]
# The width of the stroke.
# The width of the stroke. Default: 1
stroke_width: Var[Union[str, int]]
# Valid children components
@ -746,10 +738,10 @@ class ReferenceArea(Recharts):
# A boundary value of the area. If the specified y-axis is a number axis, the type of y must be Number. If the specified y-axis is a category axis, the value of y must be one of the categorys. If one of y1 or y2 is invalidate, the area will cover along y-axis.
y2: Var[Union[str, int]]
# Defines how to draw the reference line if it falls partly outside the canvas. If set to 'discard', the reference line will not be drawn at all. If set to 'hidden', the reference line will be clipped to the canvas. If set to 'visible', the reference line will be drawn completely. If set to 'extendDomain', the domain of the overflown axis will be extended such that the reference line fits into the canvas.
# Defines how to draw the reference line if it falls partly outside the canvas. If set to 'discard', the reference line will not be drawn at all. If set to 'hidden', the reference line will be clipped to the canvas. If set to 'visible', the reference line will be drawn completely. If set to 'extendDomain', the domain of the overflown axis will be extended such that the reference line fits into the canvas. Default: "discard"
if_overflow: Var[LiteralIfOverflow]
# If set true, the line will be rendered in front of bars in BarChart, etc.
# If set true, the line will be rendered in front of bars in BarChart, etc. Default: False
is_front: Var[bool]
# Valid children components
@ -779,28 +771,28 @@ class CartesianGrid(Grid):
alias = "RechartsCartesianGrid"
# The horizontal line configuration.
# The horizontal line configuration. Default: True
horizontal: Var[bool]
# The vertical line configuration.
# The vertical line configuration. Default: True
vertical: Var[bool]
# The x-coordinates in pixel values of all vertical lines.
# The x-coordinates in pixel values of all vertical lines. Default: []
vertical_points: Var[List[Union[str, int]]]
# The x-coordinates in pixel values of all vertical lines.
# The x-coordinates in pixel values of all vertical lines. Default: []
horizontal_points: Var[List[Union[str, int]]]
# The background of grid.
fill: Var[Union[str, Color]]
# The opacity of the background used to fill the space between grid lines
# The opacity of the background used to fill the space between grid lines.
fill_opacity: Var[float]
# The pattern of dashes and gaps used to paint the lines of the grid
# The pattern of dashes and gaps used to paint the lines of the grid.
stroke_dasharray: Var[str]
# the stroke color of grid
# the stroke color of grid. Default: rx.color("gray", 7)
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 7))
@ -811,28 +803,31 @@ class CartesianAxis(Grid):
alias = "RechartsCartesianAxis"
# The orientation of axis 'top' | 'bottom' | 'left' | 'right'
# The orientation of axis 'top' | 'bottom' | 'left' | 'right'. Default: "bottom"
orientation: Var[LiteralOrientationTopBottomLeftRight]
# If set false, no axis line will be drawn. If set a object, the option is the configuration of axis line.
# The box of viewing area. Default: {"x": 0, "y": 0, "width": 0, "height": 0}
view_box: Var[Dict[str, Any]]
# If set false, no axis line will be drawn. If set a object, the option is the configuration of axis line. Default: True
axis_line: Var[bool]
# If set false, no axis tick lines will be drawn. If set a object, the option is the configuration of tick lines.
# If set false, no ticks will be drawn.
tick: Var[bool]
# If set false, no axis tick lines will be drawn. If set a object, the option is the configuration of tick lines. Default: True
tick_line: Var[bool]
# The length of tick line.
# The length of tick line. Default: 6
tick_size: Var[int]
# If set 0, all the ticks will be shown. If set preserveStart", "preserveEnd" or "preserveStartEnd", the ticks which is to be shown or hidden will be calculated automatically.
# If set 0, all the ticks will be shown. If set preserveStart", "preserveEnd" or "preserveStartEnd", the ticks which is to be shown or hidden will be calculated automatically. Default: "preserveEnd"
interval: Var[LiteralInterval]
# If set false, no ticks will be drawn.
ticks: Var[bool]
# If set a string or a number, default label will be drawn, and the option is content.
label: Var[str]
label: Var[Union[str, int]]
# If set true, flips ticks around the axis line, displaying the labels inside the chart instead of outside.
# If set true, flips ticks around the axis line, displaying the labels inside the chart instead of outside. Default: False
mirror: Var[bool]
# The margin between tick line and tick.

View File

@ -182,7 +182,8 @@ class XAxis(Axis):
] = None,
x_axis_id: Optional[Union[Var[Union[int, str]], int, str]] = None,
include_hidden: Optional[Union[Var[bool], bool]] = None,
domain: Optional[Union[List, Var[List]]] = None,
angle: Optional[Union[Var[int], int]] = None,
padding: Optional[Union[Dict[str, int], Var[Dict[str, int]]]] = None,
data_key: Optional[Union[Var[Union[int, str]], int, str]] = None,
hide: Optional[Union[Var[bool], bool]] = None,
width: Optional[Union[Var[Union[int, str]], int, str]] = None,
@ -298,10 +299,11 @@ class XAxis(Axis):
Args:
*children: The children of the component.
orientation: The orientation of axis 'top' | 'bottom'
x_axis_id: The id of x-axis which is corresponding to the data.
include_hidden: Ensures that all datapoints within a chart contribute to its domain calculation, even when they are hidden
domain: The range of the axis. Work best in conjuction with allow_data_overflow.
orientation: The orientation of axis 'top' | 'bottom'. Default: "bottom"
x_axis_id: The id of x-axis which is corresponding to the data. Default: 0
include_hidden: Ensures that all datapoints within a chart contribute to its domain calculation, even when they are hidden. Default: False
angle: The angle of axis ticks. Default: 0
padding: Specify the padding of x-axis. Default: {"left": 0, "right": 0}
data_key: The key of data displayed in the axis.
hide: If set true, the axis do not display in the chart.
width: The width of axis which is usually calculated internally.
@ -348,7 +350,7 @@ class YAxis(Axis):
Union[Literal["left", "right"], Var[Literal["left", "right"]]]
] = None,
y_axis_id: Optional[Union[Var[Union[int, str]], int, str]] = None,
domain: Optional[Union[List, Var[List]]] = None,
padding: Optional[Union[Dict[str, int], Var[Dict[str, int]]]] = None,
data_key: Optional[Union[Var[Union[int, str]], int, str]] = None,
hide: Optional[Union[Var[bool], bool]] = None,
width: Optional[Union[Var[Union[int, str]], int, str]] = None,
@ -464,9 +466,9 @@ class YAxis(Axis):
Args:
*children: The children of the component.
orientation: The orientation of axis 'left' | 'right'
y_axis_id: The id of y-axis which is corresponding to the data.
domain: The range of the axis. Work best in conjuction with allow_data_overflow.
orientation: The orientation of axis 'left' | 'right'. Default: "left"
y_axis_id: The id of y-axis which is corresponding to the data. Default: 0
padding: Specify the padding of y-axis. Default: {"top": 0, "bottom": 0}
data_key: The key of data displayed in the axis.
hide: If set true, the axis do not display in the chart.
width: The width of axis which is usually calculated internally.
@ -510,6 +512,7 @@ class ZAxis(Recharts):
cls,
*children,
data_key: Optional[Union[Var[Union[int, str]], int, str]] = None,
z_axis_id: Optional[Union[Var[Union[int, str]], int, str]] = None,
range: Optional[Union[List[int], Var[List[int]]]] = None,
unit: Optional[Union[Var[Union[int, str]], int, str]] = None,
name: Optional[Union[Var[Union[int, str]], int, str]] = None,
@ -601,10 +604,11 @@ class ZAxis(Recharts):
Args:
*children: The children of the component.
data_key: The key of data displayed in the axis.
range: The range of axis.
z_axis_id: The unique id of z-axis. Default: 0
range: The range of axis. Default: [10, 10]
unit: The unit of data displayed in the axis. This option will be used to represent an index unit in a scatter chart.
name: The name of data displayed in the axis. This option will be used to represent an index in a scatter chart.
scale: If 'auto' set, the scale function is decided by the type of chart, and the props type.
scale: If 'auto' set, the scale function is decided by the type of chart, and the props type. Default: "auto"
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
@ -653,15 +657,15 @@ class Brush(Recharts):
stroke: The stroke color of brush
fill: The fill color of brush
data_key: The key of data displayed in the axis.
x: The x-coordinate of brush.
y: The y-coordinate of brush.
width: The width of brush.
height: The height of brush.
data: The data domain of brush, [min, max].
traveller_width: The width of each traveller.
gap: The data with gap of refreshing chart. If the option is not set, the chart will be refreshed every time
start_index: The default start index of brush. If the option is not set, the start index will be 0.
end_index: The default end index of brush. If the option is not set, the end index will be 1.
x: The x-coordinate of brush. Default: 0
y: The y-coordinate of brush. Default: 0
width: The width of brush. Default: 0
height: The height of brush. Default: 40
data: The original data of a LineChart, a BarChart or an AreaChart.
traveller_width: The width of each traveller. Default: 5
gap: The data with gap of refreshing chart. If the option is not set, the chart will be refreshed every time. Default: 1
start_index: The default start index of brush. If the option is not set, the start index will be 0. Default: 0
end_index: The default end index of brush. If the option is not set, the end index will be calculated by the length of data.
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
@ -843,9 +847,12 @@ class Area(Cartesian):
Union[Dict[str, Any], Var[Union[Dict[str, Any], bool]], bool]
] = None,
label: Optional[Union[Var[bool], bool]] = None,
base_line: Optional[
Union[List[Dict[str, Any]], Var[Union[List[Dict[str, Any]], str]], str]
] = None,
points: Optional[Union[List[Dict[str, Any]], Var[List[Dict[str, Any]]]]] = None,
stack_id: Optional[Union[Var[Union[int, str]], int, str]] = None,
unit: Optional[Union[Var[Union[int, str]], int, str]] = None,
name: Optional[Union[Var[Union[int, str]], int, str]] = None,
connect_nulls: Optional[Union[Var[bool], bool]] = None,
layout: Optional[
Union[
Literal["horizontal", "vertical"],
@ -934,16 +941,17 @@ class Area(Cartesian):
Args:
*children: The children of the component.
stroke: The color of the line stroke.
stroke_width: The width of the line stroke.
fill: The color of the area fill.
type_: The interpolation type of area. And customized interpolation function can be set to type. 'basis' | 'basisClosed' | 'basisOpen' | 'bumpX' | 'bumpY' | 'bump' | 'linear' | 'linearClosed' | 'natural' | 'monotoneX' | 'monotoneY' | 'monotone' | 'step' | 'stepBefore' | 'stepAfter' |
dot: If false set, dots will not be drawn. If true set, dots will be drawn which have the props calculated internally.
active_dot: The dot is shown when user enter an area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally.
label: If set false, labels will not be drawn. If set true, labels will be drawn which have the props calculated internally.
stroke: The color of the line stroke. Default: rx.color("accent", 9)
stroke_width: The width of the line stroke. Default: 1
fill: The color of the area fill. Default: rx.color("accent", 5)
type_: The interpolation type of area. And customized interpolation function can be set to type. 'basis' | 'basisClosed' | 'basisOpen' | 'bumpX' | 'bumpY' | 'bump' | 'linear' | 'linearClosed' | 'natural' | 'monotoneX' | 'monotoneY' | 'monotone' | 'step' | 'stepBefore' | 'stepAfter'. Default: "monotone"
dot: If false set, dots will not be drawn. If true set, dots will be drawn which have the props calculated internally. Default: False
active_dot: The dot is shown when user enter an area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally. Default: {stroke: rx.color("accent", 2), fill: rx.color("accent", 10)}
label: If set false, labels will not be drawn. If set true, labels will be drawn which have the props calculated internally. Default: False
base_line: The value which can describle the line, usually calculated internally.
points: The coordinates of all the points in the area, usually calculated internally.
stack_id: The stack id of area, when two areas have the same value axis and same stack_id, then the two areas are stacked in order.
unit: The unit of data. This option will be used in tooltip.
name: The name of data. This option will be used in tooltip and legend to represent a bar. If no value was set to this option, the value of dataKey will be used alternatively.
connect_nulls: Whether to connect a graph area across null points. Default: False
layout: The layout of bar in the chart, usually inherited from parent. 'horizontal' | 'vertical'
data_key: The key of a group of data which should be unique in an area chart.
x_axis_id: The id of x-axis which is corresponding to the data.
@ -979,15 +987,7 @@ class Bar(Cartesian):
name: Optional[Union[Var[Union[int, str]], int, str]] = None,
bar_size: Optional[Union[Var[int], int]] = None,
max_bar_size: Optional[Union[Var[int], int]] = None,
is_animation_active: Optional[Union[Var[bool], bool]] = None,
animation_begin: Optional[Union[Var[int], int]] = None,
animation_duration: Optional[Union[Var[int], int]] = None,
animation_easing: Optional[
Union[
Literal["ease", "ease-in", "ease-in-out", "ease-out", "linear"],
Var[Literal["ease", "ease-in", "ease-in-out", "ease-out", "linear"]],
]
] = None,
radius: Optional[Union[List[int], Var[Union[List[int], int]], int]] = None,
layout: Optional[
Union[
Literal["horizontal", "vertical"],
@ -1035,12 +1035,6 @@ class Bar(Cartesian):
class_name: Optional[Any] = None,
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_animation_end: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
on_animation_start: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
on_blur: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None,
on_click: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None,
on_context_menu: Optional[
@ -1084,19 +1078,16 @@ class Bar(Cartesian):
*children: The children of the component.
stroke: The color of the line stroke.
stroke_width: The width of the line stroke.
fill: The width of the line stroke.
background: If false set, background of bars will not be drawn. If true set, background of bars will be drawn which have the props calculated internally.
label: If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally.
fill: The width of the line stroke. Default: Color("accent", 9)
background: If false set, background of bars will not be drawn. If true set, background of bars will be drawn which have the props calculated internally. Default: False
label: If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally. Default: False
stack_id: The stack id of bar, when two bars have the same value axis and same stack_id, then the two bars are stacked in order.
unit: The unit of data. This option will be used in tooltip.
min_point_size: The minimal height of a bar in a horizontal BarChart, or the minimal width of a bar in a vertical BarChart. By default, 0 values are not shown. To visualize a 0 (or close to zero) point, set the minimal point size to a pixel value like 3. In stacked bar charts, minPointSize might not be respected for tightly packed values. So we strongly recommend not using this prop in stacked BarCharts.
name: The name of data. This option will be used in tooltip and legend to represent a bar. If no value was set to this option, the value of dataKey will be used alternatively.
bar_size: Size of the bar (if one bar_size is set then a bar_size must be set for all bars)
max_bar_size: Max size of the bar
is_animation_active: If set false, animation of bar will be disabled.
animation_begin: Specifies when the animation should begin, the unit of this option is ms, default 0.
animation_duration: Specifies the duration of animation, the unit of this option is ms, default 1500.
animation_easing: The type of easing function, default 'ease'
radius: If set a value, the option is the radius of all the rounded corners. If set a array, the option are in turn the radiuses of top-left corner, top-right corner, bottom-right corner, bottom-left corner. Default: 0
layout: The layout of bar in the chart, usually inherited from parent. 'horizontal' | 'vertical'
data_key: The key of a group of data which should be unique in an area chart.
x_axis_id: The id of x-axis which is corresponding to the data.
@ -1173,7 +1164,8 @@ class Line(Cartesian):
hide: Optional[Union[Var[bool], bool]] = None,
connect_nulls: Optional[Union[Var[bool], bool]] = None,
unit: Optional[Union[Var[Union[int, str]], int, str]] = None,
name: Optional[Union[Var[Union[int, str]], int, str]] = None,
points: Optional[Union[List[Dict[str, Any]], Var[List[Dict[str, Any]]]]] = None,
stroke_dasharray: Optional[Union[Var[str], str]] = None,
layout: Optional[
Union[
Literal["horizontal", "vertical"],
@ -1263,15 +1255,16 @@ class Line(Cartesian):
Args:
*children: The children of the component.
type_: The interpolation type of line. And customized interpolation function can be set to type. It's the same as type in Area.
stroke: The color of the line stroke.
stroke_width: The width of the line stroke.
dot: The dot is shown when mouse enter a line chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally.
active_dot: The dot is shown when user enter an area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally.
label: If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally.
hide: Hides the line when true, useful when toggling visibility state via legend.
stroke: The color of the line stroke. Default: rx.color("accent", 9)
stroke_width: The width of the line stroke. Default: 1
dot: The dot is shown when mouse enter a line chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally. Default: {"stroke": rx.color("accent", 10), "fill": rx.color("accent", 4)}
active_dot: The dot is shown when user enter an area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally. Default: {"stroke": rx.color("accent", 2), "fill": rx.color("accent", 10)}
label: If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally. Default: False
hide: Hides the line when true, useful when toggling visibility state via legend. Default: False
connect_nulls: Whether to connect a graph line across null points.
unit: The unit of data. This option will be used in tooltip.
name: The name of data displayed in the axis. This option will be used to represent an index in a scatter chart.
points: The coordinates of all the points in the line, usually calculated internally.
stroke_dasharray: The pattern of dashes and gaps used to paint the line.
layout: The layout of bar in the chart, usually inherited from parent. 'horizontal' | 'vertical'
data_key: The key of a group of data which should be unique in an area chart.
x_axis_id: The id of x-axis which is corresponding to the data.
@ -1331,7 +1324,7 @@ class Scatter(Recharts):
] = None,
x_axis_id: Optional[Union[Var[Union[int, str]], int, str]] = None,
y_axis_id: Optional[Union[Var[Union[int, str]], int, str]] = None,
z_axis_id: Optional[Union[Var[str], str]] = None,
z_axis_id: Optional[Union[Var[Union[int, str]], int, str]] = None,
line: Optional[Union[Var[bool], bool]] = None,
shape: Optional[
Union[
@ -1355,7 +1348,6 @@ class Scatter(Recharts):
Union[Literal["fitting", "joint"], Var[Literal["fitting", "joint"]]]
] = None,
fill: Optional[Union[Color, Var[Union[Color, str]], str]] = None,
name: Optional[Union[Var[Union[int, str]], int, str]] = None,
is_animation_active: Optional[Union[Var[bool], bool]] = None,
animation_begin: Optional[Union[Var[int], int]] = None,
animation_duration: Optional[Union[Var[int], int]] = None,
@ -1413,19 +1405,18 @@ class Scatter(Recharts):
Args:
*children: The children of the component.
data: The source data, in which each element is an object.
legend_type: The type of icon in legend. If set to 'none', no legend item will be rendered. 'line' | 'plainline' | 'square' | 'rect'| 'circle' | 'cross' | 'diamond' | 'square' | 'star' | 'triangle' | 'wye' | 'none'
x_axis_id: The id of x-axis which is corresponding to the data.
y_axis_id: The id of y-axis which is corresponding to the data.
z_axis_id: The id of z-axis which is corresponding to the data.
line: If false set, line will not be drawn. If true set, line will be drawn which have the props calculated internally.
shape: If a string set, specified symbol will be used to show scatter item. 'circle' | 'cross' | 'diamond' | 'square' | 'star' | 'triangle' | 'wye'
line_type: If 'joint' set, line will generated by just jointing all the points. If 'fitting' set, line will be generated by fitting algorithm. 'joint' | 'fitting'
fill: The fill
name: the name
is_animation_active: If set false, animation of bar will be disabled.
animation_begin: Specifies when the animation should begin, the unit of this option is ms, default 0.
animation_duration: Specifies the duration of animation, the unit of this option is ms, default 1500.
animation_easing: The type of easing function, default 'ease'
legend_type: The type of icon in legend. If set to 'none', no legend item will be rendered. 'line' | 'plainline' | 'square' | 'rect'| 'circle' | 'cross' | 'diamond' | 'square' | 'star' | 'triangle' | 'wye' | 'none'. Default: "circle"
x_axis_id: The id of x-axis which is corresponding to the data. Default: 0
y_axis_id: The id of y-axis which is corresponding to the data. Default: 0
z_axis_id: The id of z-axis which is corresponding to the data. Default: 0
line: If false set, line will not be drawn. If true set, line will be drawn which have the props calculated internally. Default: False
shape: If a string set, specified symbol will be used to show scatter item. 'circle' | 'cross' | 'diamond' | 'square' | 'star' | 'triangle' | 'wye'. Default: "circle"
line_type: If 'joint' set, line will generated by just jointing all the points. If 'fitting' set, line will be generated by fitting algorithm. 'joint' | 'fitting'. Default: "joint"
fill: The fill color of the scatter. Default: rx.color("accent", 9)
is_animation_active: If set false, animation of bar will be disabled. Default: True in CSR, False in SSR
animation_begin: Specifies when the animation should begin, the unit of this option is ms. Default: 0
animation_duration: Specifies the duration of animation, the unit of this option is ms. Default: 1500
animation_easing: The type of easing function. Default: "ease"
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
@ -1706,11 +1697,11 @@ class Reference(Recharts):
Args:
*children: The children of the component.
x_axis_id: The id of x-axis which is corresponding to the data.
y_axis_id: The id of y-axis which is corresponding to the data.
if_overflow: Defines how to draw the reference line if it falls partly outside the canvas. If set to 'discard', the reference line will not be drawn at all. If set to 'hidden', the reference line will be clipped to the canvas. If set to 'visible', the reference line will be drawn completely. If set to 'extendDomain', the domain of the overflown axis will be extended such that the reference line fits into the canvas.
x_axis_id: The id of x-axis which is corresponding to the data. Default: 0
y_axis_id: The id of y-axis which is corresponding to the data. Default: 0
if_overflow: Defines how to draw the reference line if it falls partly outside the canvas. If set to 'discard', the reference line will not be drawn at all. If set to 'hidden', the reference line will be clipped to the canvas. If set to 'visible', the reference line will be drawn completely. If set to 'extendDomain', the domain of the overflown axis will be extended such that the reference line fits into the canvas. Default: "discard"
label: If set a string or a number, default label will be drawn, and the option is content.
is_front: If set true, the line will be rendered in front of bars in BarChart, etc.
is_front: If set true, the line will be rendered in front of bars in BarChart, etc. Default: False
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
@ -1795,13 +1786,13 @@ class ReferenceLine(Reference):
x: If set a string or a number, a vertical line perpendicular to the x-axis specified by xAxisId will be drawn. If the specified x-axis is a number axis, the type of x must be Number. If the specified x-axis is a category axis, the value of x must be one of the categorys, otherwise no line will be drawn.
y: If set a string or a number, a horizontal line perpendicular to the y-axis specified by yAxisId will be drawn. If the specified y-axis is a number axis, the type of y must be Number. If the specified y-axis is a category axis, the value of y must be one of the categorys, otherwise no line will be drawn.
stroke: The color of the reference line.
stroke_width: The width of the stroke.
stroke_width: The width of the stroke. Default: 1
segment: Array of endpoints in { x, y } format. These endpoints would be used to draw the ReferenceLine.
x_axis_id: The id of x-axis which is corresponding to the data.
y_axis_id: The id of y-axis which is corresponding to the data.
if_overflow: Defines how to draw the reference line if it falls partly outside the canvas. If set to 'discard', the reference line will not be drawn at all. If set to 'hidden', the reference line will be clipped to the canvas. If set to 'visible', the reference line will be drawn completely. If set to 'extendDomain', the domain of the overflown axis will be extended such that the reference line fits into the canvas.
x_axis_id: The id of x-axis which is corresponding to the data. Default: 0
y_axis_id: The id of y-axis which is corresponding to the data. Default: 0
if_overflow: Defines how to draw the reference line if it falls partly outside the canvas. If set to 'discard', the reference line will not be drawn at all. If set to 'hidden', the reference line will be clipped to the canvas. If set to 'visible', the reference line will be drawn completely. If set to 'extendDomain', the domain of the overflown axis will be extended such that the reference line fits into the canvas. Default: "discard"
label: If set a string or a number, default label will be drawn, and the option is content.
is_front: If set true, the line will be rendered in front of bars in BarChart, etc.
is_front: If set true, the line will be rendered in front of bars in BarChart, etc. Default: False
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
@ -1888,11 +1879,11 @@ class ReferenceDot(Reference):
r: The radius of dot.
fill: The color of the area fill.
stroke: The color of the line stroke.
x_axis_id: The id of x-axis which is corresponding to the data.
y_axis_id: The id of y-axis which is corresponding to the data.
if_overflow: Defines how to draw the reference line if it falls partly outside the canvas. If set to 'discard', the reference line will not be drawn at all. If set to 'hidden', the reference line will be clipped to the canvas. If set to 'visible', the reference line will be drawn completely. If set to 'extendDomain', the domain of the overflown axis will be extended such that the reference line fits into the canvas.
x_axis_id: The id of x-axis which is corresponding to the data. Default: 0
y_axis_id: The id of y-axis which is corresponding to the data. Default: 0
if_overflow: Defines how to draw the reference line if it falls partly outside the canvas. If set to 'discard', the reference line will not be drawn at all. If set to 'hidden', the reference line will be clipped to the canvas. If set to 'visible', the reference line will be drawn completely. If set to 'extendDomain', the domain of the overflown axis will be extended such that the reference line fits into the canvas. Default: "discard"
label: If set a string or a number, default label will be drawn, and the option is content.
is_front: If set true, the line will be rendered in front of bars in BarChart, etc.
is_front: If set true, the line will be rendered in front of bars in BarChart, etc. Default: False
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
@ -1984,8 +1975,8 @@ class ReferenceArea(Recharts):
x2: A boundary value of the area. If the specified x-axis is a number axis, the type of x must be Number. If the specified x-axis is a category axis, the value of x must be one of the categorys. If one of x1 or x2 is invalidate, the area will cover along x-axis.
y1: A boundary value of the area. If the specified y-axis is a number axis, the type of y must be Number. If the specified y-axis is a category axis, the value of y must be one of the categorys. If one of y1 or y2 is invalidate, the area will cover along y-axis.
y2: A boundary value of the area. If the specified y-axis is a number axis, the type of y must be Number. If the specified y-axis is a category axis, the value of y must be one of the categorys. If one of y1 or y2 is invalidate, the area will cover along y-axis.
if_overflow: Defines how to draw the reference line if it falls partly outside the canvas. If set to 'discard', the reference line will not be drawn at all. If set to 'hidden', the reference line will be clipped to the canvas. If set to 'visible', the reference line will be drawn completely. If set to 'extendDomain', the domain of the overflown axis will be extended such that the reference line fits into the canvas.
is_front: If set true, the line will be rendered in front of bars in BarChart, etc.
if_overflow: Defines how to draw the reference line if it falls partly outside the canvas. If set to 'discard', the reference line will not be drawn at all. If set to 'hidden', the reference line will be clipped to the canvas. If set to 'visible', the reference line will be drawn completely. If set to 'extendDomain', the domain of the overflown axis will be extended such that the reference line fits into the canvas. Default: "discard"
is_front: If set true, the line will be rendered in front of bars in BarChart, etc. Default: False
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
@ -2142,14 +2133,14 @@ class CartesianGrid(Grid):
Args:
*children: The children of the component.
horizontal: The horizontal line configuration.
vertical: The vertical line configuration.
vertical_points: The x-coordinates in pixel values of all vertical lines.
horizontal_points: The x-coordinates in pixel values of all vertical lines.
horizontal: The horizontal line configuration. Default: True
vertical: The vertical line configuration. Default: True
vertical_points: The x-coordinates in pixel values of all vertical lines. Default: []
horizontal_points: The x-coordinates in pixel values of all vertical lines. Default: []
fill: The background of grid.
fill_opacity: The opacity of the background used to fill the space between grid lines
stroke_dasharray: The pattern of dashes and gaps used to paint the lines of the grid
stroke: the stroke color of grid
fill_opacity: The opacity of the background used to fill the space between grid lines.
stroke_dasharray: The pattern of dashes and gaps used to paint the lines of the grid.
stroke: the stroke color of grid. Default: rx.color("gray", 7)
x: The x-coordinate of grid.
y: The y-coordinate of grid.
width: The width of grid.
@ -2179,7 +2170,9 @@ class CartesianAxis(Grid):
Var[Literal["bottom", "left", "right", "top"]],
]
] = None,
view_box: Optional[Union[Dict[str, Any], Var[Dict[str, Any]]]] = None,
axis_line: Optional[Union[Var[bool], bool]] = None,
tick: Optional[Union[Var[bool], bool]] = None,
tick_line: Optional[Union[Var[bool], bool]] = None,
tick_size: Optional[Union[Var[int], int]] = None,
interval: Optional[
@ -2188,8 +2181,7 @@ class CartesianAxis(Grid):
Var[Literal["preserveEnd", "preserveStart", "preserveStartEnd"]],
]
] = None,
ticks: Optional[Union[Var[bool], bool]] = None,
label: Optional[Union[Var[str], str]] = None,
label: Optional[Union[Var[Union[int, str]], int, str]] = None,
mirror: Optional[Union[Var[bool], bool]] = None,
tick_margin: Optional[Union[Var[int], int]] = None,
x: Optional[Union[Var[int], int]] = None,
@ -2243,14 +2235,15 @@ class CartesianAxis(Grid):
Args:
*children: The children of the component.
orientation: The orientation of axis 'top' | 'bottom' | 'left' | 'right'
axis_line: If set false, no axis line will be drawn. If set a object, the option is the configuration of axis line.
tick_line: If set false, no axis tick lines will be drawn. If set a object, the option is the configuration of tick lines.
tick_size: The length of tick line.
interval: If set 0, all the ticks will be shown. If set preserveStart", "preserveEnd" or "preserveStartEnd", the ticks which is to be shown or hidden will be calculated automatically.
ticks: If set false, no ticks will be drawn.
orientation: The orientation of axis 'top' | 'bottom' | 'left' | 'right'. Default: "bottom"
view_box: The box of viewing area. Default: {"x": 0, "y": 0, "width": 0, "height": 0}
axis_line: If set false, no axis line will be drawn. If set a object, the option is the configuration of axis line. Default: True
tick: If set false, no ticks will be drawn.
tick_line: If set false, no axis tick lines will be drawn. If set a object, the option is the configuration of tick lines. Default: True
tick_size: The length of tick line. Default: 6
interval: If set 0, all the ticks will be shown. If set preserveStart", "preserveEnd" or "preserveStartEnd", the ticks which is to be shown or hidden will be calculated automatically. Default: "preserveEnd"
label: If set a string or a number, default label will be drawn, and the option is content.
mirror: If set true, flips ticks around the axis line, displaying the labels inside the chart instead of outside.
mirror: If set true, flips ticks around the axis line, displaying the labels inside the chart instead of outside. Default: False
tick_margin: The margin between tick line and tick.
x: The x-coordinate of grid.
y: The y-coordinate of grid.

View File

@ -9,7 +9,7 @@ from reflex.components.recharts.general import ResponsiveContainer
from reflex.constants import EventTriggers
from reflex.constants.colors import Color
from reflex.event import EventHandler
from reflex.vars.base import LiteralVar, Var
from reflex.vars.base import Var
from .recharts import (
LiteralAnimationEasing,
@ -112,10 +112,10 @@ class CategoricalChartBase(ChartBase):
# If any two categorical charts(rx.line_chart, rx.area_chart, rx.bar_chart, rx.composed_chart) have the same sync_id, these two charts can sync the position GraphingTooltip, and the start_index, end_index of Brush.
sync_id: Var[str]
# When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function
# When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function. Default: "index"
sync_method: Var[LiteralSyncMethod]
# The layout of area in the chart. 'horizontal' | 'vertical'
# The layout of area in the chart. 'horizontal' | 'vertical'. Default: "horizontal"
layout: Var[LiteralLayout]
# The type of offset function used to generate the lower and upper values in the series array. The four types are built-in offsets in d3-shape. 'expand' | 'none' | 'wiggle' | 'silhouette'
@ -129,7 +129,7 @@ class AreaChart(CategoricalChartBase):
alias = "RechartsAreaChart"
# The base value of area. Number | 'dataMin' | 'dataMax' | 'auto'
# The base value of area. Number | 'dataMin' | 'dataMax' | 'auto'. Default: "auto"
base_value: Var[Union[int, LiteralComposedChartBaseValue]]
# Valid children components
@ -155,11 +155,11 @@ class BarChart(CategoricalChartBase):
alias = "RechartsBarChart"
# The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number
bar_category_gap: Var[Union[str, int]] = LiteralVar.create("10%")
# The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number. Default: "10%"
bar_category_gap: Var[Union[str, int]]
# The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number
bar_gap: Var[Union[str, int]] = LiteralVar.create(4) # type: ignore
# The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number. Default: 4
bar_gap: Var[Union[str, int]]
# The width of all the bars in the chart. Number
bar_size: Var[int]
@ -167,10 +167,10 @@ class BarChart(CategoricalChartBase):
# The maximum width of all the bars in a horizontal BarChart, or maximum height in a vertical BarChart.
max_bar_size: Var[int]
# The type of offset function used to generate the lower and upper values in the series array. The four types are built-in offsets in d3-shape.
# The type of offset function used to generate the lower and upper values in the series array. The four types are built-in offsets in d3-shape. Default: "none"
stack_offset: Var[LiteralStackOffset]
# If false set, stacked items will be rendered left to right. If true set, stacked items will be rendered right to left. (Render direction affects SVG layering, not x position.)
# If false set, stacked items will be rendered left to right. If true set, stacked items will be rendered right to left. (Render direction affects SVG layering, not x position.) Default: False
reverse_stack_order: Var[bool]
# Valid children components
@ -217,19 +217,19 @@ class ComposedChart(CategoricalChartBase):
alias = "RechartsComposedChart"
# The base value of area. Number | 'dataMin' | 'dataMax' | 'auto'
# The base value of area. Number | 'dataMin' | 'dataMax' | 'auto'. Default: "auto"
base_value: Var[Union[int, LiteralComposedChartBaseValue]]
# The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number
bar_category_gap: Var[Union[str, int]] # type: ignore
# The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number. Default: "10%"
bar_category_gap: Var[Union[str, int]]
# The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number
bar_gap: Var[Union[str, int]] # type: ignore
# The gap between two bars in the same category. Default: 4
bar_gap: Var[int]
# The width of all the bars in the chart. Number
# The width or height of each bar. If the barSize is not specified, the size of the bar will be calculated by the barCategoryGap, barGap and the quantity of bar groups.
bar_size: Var[int]
# If false set, stacked items will be rendered left to right. If true set, stacked items will be rendered right to left. (Render direction affects SVG layering, not x position.)
# If false set, stacked items will be rendered left to right. If true set, stacked items will be rendered right to left. (Render direction affects SVG layering, not x position). Default: False
reverse_stack_order: Var[bool]
# Valid children components
@ -292,25 +292,25 @@ class RadarChart(ChartBase):
# The source data, in which each element is an object.
data: Var[List[Dict[str, Any]]]
# The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
# The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}. Default: {"top": 0, "right": 0, "left": 0, "bottom": 0}
margin: Var[Dict[str, Any]]
# The The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of width. Number | Percentage
# The The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of width. Number | Percentage. Default: "50%"
cx: Var[Union[int, str]]
# The The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of height. Number | Percentage
# The The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of height. Number | Percentage. Default: "50%"
cy: Var[Union[int, str]]
# The angle of first radial direction line.
# The angle of first radial direction line. Default: 90
start_angle: Var[int]
# The angle of last point in the circle which should be startAngle - 360 or startAngle + 360. We'll calculate the direction of chart by 'startAngle' and 'endAngle'.
# The angle of last point in the circle which should be startAngle - 360 or startAngle + 360. We'll calculate the direction of chart by 'startAngle' and 'endAngle'. Default: -270
end_angle: Var[int]
# The inner radius of first circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage
# The inner radius of first circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage. Default: 0
inner_radius: Var[Union[int, str]]
# The outer radius of last circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage
# The outer radius of last circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage. Default: "80%"
outer_radius: Var[Union[int, str]]
# Valid children components
@ -346,31 +346,31 @@ class RadialBarChart(ChartBase):
# The source data which each element is an object.
data: Var[List[Dict[str, Any]]]
# The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
# The sizes of whitespace around the chart. Default: {"top": 5, "right": 5, "left": 5 "bottom": 5}
margin: Var[Dict[str, Any]]
# The The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of width. Number | Percentage
# The The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of width. Number | Percentage. Default: "50%"
cx: Var[Union[int, str]]
# The The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of height. Number | Percentage
# The The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of height. Number | Percentage. Default: "50%"
cy: Var[Union[int, str]]
# The angle of first radial direction line.
# The angle of first radial direction line. Default: 0
start_angle: Var[int]
# The angle of last point in the circle which should be startAngle - 360 or startAngle + 360. We'll calculate the direction of chart by 'startAngle' and 'endAngle'.
# The angle of last point in the circle which should be startAngle - 360 or startAngle + 360. We'll calculate the direction of chart by 'startAngle' and 'endAngle'. Default: 360
end_angle: Var[int]
# The inner radius of first circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage
# The inner radius of first circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage. Default: "30%"
inner_radius: Var[Union[int, str]]
# The outer radius of last circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage
# The outer radius of last circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage. Default: "100%"
outer_radius: Var[Union[int, str]]
# The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number
# The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number. Default: "10%"
bar_category_gap: Var[Union[int, str]]
# The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number
# The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number. Default: 4
bar_gap: Var[str]
# The size of each bar. If the barSize is not specified, the size of bar will be calculated by the barCategoryGap, barGap and the quantity of bar groups.
@ -394,7 +394,7 @@ class ScatterChart(ChartBase):
alias = "RechartsScatterChart"
# The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
# The sizes of whitespace around the chart. Default: {"top": 5, "right": 5, "bottom": 5, "left": 5}
margin: Var[Dict[str, Any]]
# Valid children components
@ -437,10 +437,10 @@ class FunnelChart(ChartBase):
alias = "RechartsFunnelChart"
# The layout of bars in the chart. centeric
# The layout of bars in the chart. Default: "centric"
layout: Var[str]
# The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
# The sizes of whitespace around the chart. Default: {"top": 5, "right": 5, "bottom": 5, "left": 5}
margin: Var[Dict[str, Any]]
# The stroke color of each bar. String | Object
@ -457,31 +457,34 @@ class Treemap(RechartsCharts):
alias = "RechartsTreemap"
# The width of chart container. String or Integer
# The width of chart container. String or Integer. Default: "100%"
width: Var[Union[str, int]] = "100%" # type: ignore
# The height of chart container.
# The height of chart container. String or Integer. Default: "100%"
height: Var[Union[str, int]] = "100%" # type: ignore
# data of treemap. Array
data: Var[List[Dict[str, Any]]]
# The key of a group of data which should be unique in a treemap. String | Number | Function
# The key of a group of data which should be unique in a treemap. String | Number. Default: "value"
data_key: Var[Union[str, int]]
# The key of each sector's name. String. Default: "name"
name_key: Var[str]
# The treemap will try to keep every single rectangle's aspect ratio near the aspectRatio given. Number
aspect_ratio: Var[int]
# If set false, animation of area will be disabled.
# If set false, animation of area will be disabled. Default: True
is_animation_active: Var[bool]
# Specifies when the animation should begin, the unit of this option is ms.
# Specifies when the animation should begin, the unit of this option is ms. Default: 0
animation_begin: Var[int]
# Specifies the duration of animation, the unit of this option is ms.
# Specifies the duration of animation, the unit of this option is ms. Default: 1500
animation_duration: Var[int]
# The type of easing function. 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear'
# The type of easing function. 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear'. Default: "ease"
animation_easing: Var[LiteralAnimationEasing]
# The customized event handler of animation start

View File

@ -160,8 +160,8 @@ class CategoricalChartBase(ChartBase):
data: The source data, in which each element is an object.
margin: The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
sync_id: If any two categorical charts(rx.line_chart, rx.area_chart, rx.bar_chart, rx.composed_chart) have the same sync_id, these two charts can sync the position GraphingTooltip, and the start_index, end_index of Brush.
sync_method: When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function
layout: The layout of area in the chart. 'horizontal' | 'vertical'
sync_method: When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function. Default: "index"
layout: The layout of area in the chart. 'horizontal' | 'vertical'. Default: "horizontal"
stack_offset: The type of offset function used to generate the lower and upper values in the series array. The four types are built-in offsets in d3-shape. 'expand' | 'none' | 'wiggle' | 'silhouette'
width: The width of chart container. String or Integer
height: The height of chart container.
@ -258,12 +258,12 @@ class AreaChart(CategoricalChartBase):
Args:
*children: The children of the chart component.
base_value: The base value of area. Number | 'dataMin' | 'dataMax' | 'auto'
base_value: The base value of area. Number | 'dataMin' | 'dataMax' | 'auto'. Default: "auto"
data: The source data, in which each element is an object.
margin: The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
sync_id: If any two categorical charts(rx.line_chart, rx.area_chart, rx.bar_chart, rx.composed_chart) have the same sync_id, these two charts can sync the position GraphingTooltip, and the start_index, end_index of Brush.
sync_method: When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function
layout: The layout of area in the chart. 'horizontal' | 'vertical'
sync_method: When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function. Default: "index"
layout: The layout of area in the chart. 'horizontal' | 'vertical'. Default: "horizontal"
stack_offset: The type of offset function used to generate the lower and upper values in the series array. The four types are built-in offsets in d3-shape. 'expand' | 'none' | 'wiggle' | 'silhouette'
width: The width of chart container. String or Integer
height: The height of chart container.
@ -358,17 +358,17 @@ class BarChart(CategoricalChartBase):
Args:
*children: The children of the chart component.
bar_category_gap: The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number
bar_gap: The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number
bar_category_gap: The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number. Default: "10%"
bar_gap: The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number. Default: 4
bar_size: The width of all the bars in the chart. Number
max_bar_size: The maximum width of all the bars in a horizontal BarChart, or maximum height in a vertical BarChart.
stack_offset: The type of offset function used to generate the lower and upper values in the series array. The four types are built-in offsets in d3-shape. 'expand' | 'none' | 'wiggle' | 'silhouette'
reverse_stack_order: If false set, stacked items will be rendered left to right. If true set, stacked items will be rendered right to left. (Render direction affects SVG layering, not x position.)
reverse_stack_order: If false set, stacked items will be rendered left to right. If true set, stacked items will be rendered right to left. (Render direction affects SVG layering, not x position.) Default: False
data: The source data, in which each element is an object.
margin: The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
sync_id: If any two categorical charts(rx.line_chart, rx.area_chart, rx.bar_chart, rx.composed_chart) have the same sync_id, these two charts can sync the position GraphingTooltip, and the start_index, end_index of Brush.
sync_method: When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function
layout: The layout of area in the chart. 'horizontal' | 'vertical'
sync_method: When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function. Default: "index"
layout: The layout of area in the chart. 'horizontal' | 'vertical'. Default: "horizontal"
width: The width of chart container. String or Integer
height: The height of chart container.
style: The style of the component.
@ -460,8 +460,8 @@ class LineChart(CategoricalChartBase):
data: The source data, in which each element is an object.
margin: The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
sync_id: If any two categorical charts(rx.line_chart, rx.area_chart, rx.bar_chart, rx.composed_chart) have the same sync_id, these two charts can sync the position GraphingTooltip, and the start_index, end_index of Brush.
sync_method: When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function
layout: The layout of area in the chart. 'horizontal' | 'vertical'
sync_method: When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function. Default: "index"
layout: The layout of area in the chart. 'horizontal' | 'vertical'. Default: "horizontal"
stack_offset: The type of offset function used to generate the lower and upper values in the series array. The four types are built-in offsets in d3-shape. 'expand' | 'none' | 'wiggle' | 'silhouette'
width: The width of chart container. String or Integer
height: The height of chart container.
@ -492,7 +492,7 @@ class ComposedChart(CategoricalChartBase):
]
] = None,
bar_category_gap: Optional[Union[Var[Union[int, str]], int, str]] = None,
bar_gap: Optional[Union[Var[Union[int, str]], int, str]] = None,
bar_gap: Optional[Union[Var[int], int]] = None,
bar_size: Optional[Union[Var[int], int]] = None,
reverse_stack_order: Optional[Union[Var[bool], bool]] = None,
data: Optional[Union[List[Dict[str, Any]], Var[List[Dict[str, Any]]]]] = None,
@ -562,16 +562,16 @@ class ComposedChart(CategoricalChartBase):
Args:
*children: The children of the chart component.
base_value: The base value of area. Number | 'dataMin' | 'dataMax' | 'auto'
bar_category_gap: The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number
bar_gap: The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number
bar_size: The width of all the bars in the chart. Number
reverse_stack_order: If false set, stacked items will be rendered left to right. If true set, stacked items will be rendered right to left. (Render direction affects SVG layering, not x position.)
base_value: The base value of area. Number | 'dataMin' | 'dataMax' | 'auto'. Default: "auto"
bar_category_gap: The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number. Default: "10%"
bar_gap: The gap between two bars in the same category. Default: 4
bar_size: The width or height of each bar. If the barSize is not specified, the size of the bar will be calculated by the barCategoryGap, barGap and the quantity of bar groups.
reverse_stack_order: If false set, stacked items will be rendered left to right. If true set, stacked items will be rendered right to left. (Render direction affects SVG layering, not x position). Default: False
data: The source data, in which each element is an object.
margin: The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
sync_id: If any two categorical charts(rx.line_chart, rx.area_chart, rx.bar_chart, rx.composed_chart) have the same sync_id, these two charts can sync the position GraphingTooltip, and the start_index, end_index of Brush.
sync_method: When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function
layout: The layout of area in the chart. 'horizontal' | 'vertical'
sync_method: When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function. Default: "index"
layout: The layout of area in the chart. 'horizontal' | 'vertical'. Default: "horizontal"
stack_offset: The type of offset function used to generate the lower and upper values in the series array. The four types are built-in offsets in d3-shape. 'expand' | 'none' | 'wiggle' | 'silhouette'
width: The width of chart container. String or Integer
height: The height of chart container.
@ -697,13 +697,13 @@ class RadarChart(ChartBase):
Args:
*children: The children of the chart component.
data: The source data, in which each element is an object.
margin: The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
cx: The The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of width. Number | Percentage
cy: The The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of height. Number | Percentage
start_angle: The angle of first radial direction line.
end_angle: The angle of last point in the circle which should be startAngle - 360 or startAngle + 360. We'll calculate the direction of chart by 'startAngle' and 'endAngle'.
inner_radius: The inner radius of first circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage
outer_radius: The outer radius of last circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage
margin: The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}. Default: {"top": 0, "right": 0, "left": 0, "bottom": 0}
cx: The The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of width. Number | Percentage. Default: "50%"
cy: The The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of height. Number | Percentage. Default: "50%"
start_angle: The angle of first radial direction line. Default: 90
end_angle: The angle of last point in the circle which should be startAngle - 360 or startAngle + 360. We'll calculate the direction of chart by 'startAngle' and 'endAngle'. Default: -270
inner_radius: The inner radius of first circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage. Default: 0
outer_radius: The outer radius of last circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage. Default: "80%"
width: The width of chart container. String or Integer
height: The height of chart container.
style: The style of the component.
@ -786,15 +786,15 @@ class RadialBarChart(ChartBase):
Args:
*children: The children of the chart component.
data: The source data which each element is an object.
margin: The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
cx: The The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of width. Number | Percentage
cy: The The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of height. Number | Percentage
start_angle: The angle of first radial direction line.
end_angle: The angle of last point in the circle which should be startAngle - 360 or startAngle + 360. We'll calculate the direction of chart by 'startAngle' and 'endAngle'.
inner_radius: The inner radius of first circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage
outer_radius: The outer radius of last circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage
bar_category_gap: The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number
bar_gap: The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number
margin: The sizes of whitespace around the chart. Default: {"top": 5, "right": 5, "left": 5 "bottom": 5}
cx: The The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of width. Number | Percentage. Default: "50%"
cy: The The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of height. Number | Percentage. Default: "50%"
start_angle: The angle of first radial direction line. Default: 0
end_angle: The angle of last point in the circle which should be startAngle - 360 or startAngle + 360. We'll calculate the direction of chart by 'startAngle' and 'endAngle'. Default: 360
inner_radius: The inner radius of first circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage. Default: "30%"
outer_radius: The outer radius of last circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage. Default: "100%"
bar_category_gap: The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number. Default: "10%"
bar_gap: The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number. Default: 4
bar_size: The size of each bar. If the barSize is not specified, the size of bar will be calculated by the barCategoryGap, barGap and the quantity of bar groups.
width: The width of chart container. String or Integer
height: The height of chart container.
@ -855,7 +855,7 @@ class ScatterChart(ChartBase):
Args:
*children: The children of the chart component.
margin: The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
margin: The sizes of whitespace around the chart. Default: {"top": 5, "right": 5, "bottom": 5, "left": 5}
width: The width of chart container. String or Integer
height: The height of chart container.
style: The style of the component.
@ -929,8 +929,8 @@ class FunnelChart(ChartBase):
Args:
*children: The children of the chart component.
layout: The layout of bars in the chart. centeric
margin: The sizes of whitespace around the chart, i.e. {"top": 50, "right": 30, "left": 20, "bottom": 5}.
layout: The layout of bars in the chart. Default: "centric"
margin: The sizes of whitespace around the chart. Default: {"top": 5, "right": 5, "bottom": 5, "left": 5}
stroke: The stroke color of each bar. String | Object
width: The width of chart container. String or Integer
height: The height of chart container.
@ -957,6 +957,7 @@ class Treemap(RechartsCharts):
height: Optional[Union[Var[Union[int, str]], int, str]] = None,
data: Optional[Union[List[Dict[str, Any]], Var[List[Dict[str, Any]]]]] = None,
data_key: Optional[Union[Var[Union[int, str]], int, str]] = None,
name_key: Optional[Union[Var[str], str]] = None,
aspect_ratio: Optional[Union[Var[int], int]] = None,
is_animation_active: Optional[Union[Var[bool], bool]] = None,
animation_begin: Optional[Union[Var[int], int]] = None,
@ -1020,15 +1021,16 @@ class Treemap(RechartsCharts):
Args:
*children: The children of the chart component.
width: The width of chart container. String or Integer
height: The height of chart container.
width: The width of chart container. String or Integer. Default: "100%"
height: The height of chart container. String or Integer. Default: "100%"
data: data of treemap. Array
data_key: The key of a group of data which should be unique in a treemap. String | Number | Function
data_key: The key of a group of data which should be unique in a treemap. String | Number. Default: "value"
name_key: The key of each sector's name. String. Default: "name"
aspect_ratio: The treemap will try to keep every single rectangle's aspect ratio near the aspectRatio given. Number
is_animation_active: If set false, animation of area will be disabled.
animation_begin: Specifies when the animation should begin, the unit of this option is ms.
animation_duration: Specifies the duration of animation, the unit of this option is ms.
animation_easing: The type of easing function. 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear'
is_animation_active: If set false, animation of area will be disabled. Default: True
animation_begin: Specifies when the animation should begin, the unit of this option is ms. Default: 0
animation_duration: Specifies the duration of animation, the unit of this option is ms. Default: 1500
animation_easing: The type of easing function. 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear'. Default: "ease"
style: The style of the component.
key: A unique key for the component.
id: The id for the component.

View File

@ -30,21 +30,24 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
# The aspect ratio of the container. The final aspect ratio of the SVG element will be (width / height) * aspect. Number
aspect: Var[int]
# The width of chart container. Can be a number or string
# The width of chart container. Can be a number or string. Default: "100%"
width: Var[Union[int, str]]
# The height of chart container. Number
# The height of chart container. Can be a number or string. Default: "100%"
height: Var[Union[int, str]]
# The minimum width of chart container.
# The minimum width of chart container. Number
min_width: Var[int]
# The minimum height of chart container. Number
min_height: Var[int]
# If specified a positive number, debounced function will be used to handle the resize event.
# If specified a positive number, debounced function will be used to handle the resize event. Default: 0
debounce: Var[int]
# If specified provides a callback providing the updated chart width and height values.
on_resize: EventHandler[lambda: []]
# Valid children components
_valid_children: List[str] = [
"AreaChart",
@ -73,21 +76,24 @@ class Legend(Recharts):
# The height of legend container. Number
height: Var[int]
# The layout of legend items. 'horizontal' | 'vertical'
# The layout of legend items. 'horizontal' | 'vertical'. Default: "horizontal"
layout: Var[LiteralLayout]
# The alignment of legend items in 'horizontal' direction, which can be 'left', 'center', 'right'.
# The alignment of legend items in 'horizontal' direction, which can be 'left', 'center', 'right'. Default: "center"
align: Var[LiteralLegendAlign]
# The alignment of legend items in 'vertical' direction, which can be 'top', 'middle', 'bottom'.
# The alignment of legend items in 'vertical' direction, which can be 'top', 'middle', 'bottom'. Default: "bottom"
vertical_align: Var[LiteralVerticalAlign]
# The size of icon in each legend item.
# The size of icon in each legend item. Default: 14
icon_size: Var[int]
# The type of icon in each legend item. 'line' | 'plainline' | 'square' | 'rect' | 'circle' | 'cross' | 'diamond' | 'star' | 'triangle' | 'wye'
icon_type: Var[LiteralIconType]
# The source data of the content to be displayed in the legend, usually calculated internally. Default: []
payload: Var[List[Dict[str, Any]]]
# The width of chart container, usually calculated internally.
chart_width: Var[int]
@ -224,16 +230,16 @@ class LabelList(Recharts):
# The key of a group of label values in data.
data_key: Var[Union[str, int]]
# The position of each label relative to it view box"Top" | "left" | "right" | "bottom" | "inside" | "outside" | "insideLeft" | "insideRight" | "insideTop" | "insideBottom" | "insideTopLeft" | "insideBottomLeft" | "insideTopRight" | "insideBottomRight" | "insideStart" | "insideEnd" | "end" | "center"
# The position of each label relative to it view box. "Top" | "left" | "right" | "bottom" | "inside" | "outside" | "insideLeft" | "insideRight" | "insideTop" | "insideBottom" | "insideTopLeft" | "insideBottomLeft" | "insideTopRight" | "insideBottomRight" | "insideStart" | "insideEnd" | "end" | "center"
position: Var[LiteralPosition]
# The offset to the specified "position"
# The offset to the specified "position". Default: 5
offset: Var[int]
# The fill color of each label
# The fill color of each label. Default: rx.color("gray", 10)
fill: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 10))
# The stroke color of each label
# The stroke color of each label. Default: "none"
stroke: Var[Union[str, Color]] = LiteralVar.create("none")

View File

@ -3,7 +3,7 @@
# ------------------- DO NOT EDIT ----------------------
# This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------
from typing import Any, Callable, Dict, Literal, Optional, Union, overload
from typing import Any, Callable, Dict, List, Literal, Optional, Union, overload
from reflex.components.component import MemoizationLeaf
from reflex.constants.colors import Color
@ -64,6 +64,7 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
on_mouse_up: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
on_resize: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None,
on_scroll: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None,
on_unmount: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
@ -75,11 +76,11 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
Args:
*children: The children of the component.
aspect: The aspect ratio of the container. The final aspect ratio of the SVG element will be (width / height) * aspect. Number
width: The width of chart container. Can be a number or string
height: The height of chart container. Number
min_width: The minimum width of chart container.
width: The width of chart container. Can be a number or string. Default: "100%"
height: The height of chart container. Can be a number or string. Default: "100%"
min_width: The minimum width of chart container. Number
min_height: The minimum height of chart container. Number
debounce: If specified a positive number, debounced function will be used to handle the resize event.
debounce: If specified a positive number, debounced function will be used to handle the resize event. Default: 0
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
@ -150,6 +151,9 @@ class Legend(Recharts):
],
]
] = None,
payload: Optional[
Union[List[Dict[str, Any]], Var[List[Dict[str, Any]]]]
] = None,
chart_width: Optional[Union[Var[int], int]] = None,
chart_height: Optional[Union[Var[int], int]] = None,
margin: Optional[Union[Dict[str, Any], Var[Dict[str, Any]]]] = None,
@ -202,11 +206,12 @@ class Legend(Recharts):
*children: The children of the component.
width: The width of legend container. Number
height: The height of legend container. Number
layout: The layout of legend items. 'horizontal' | 'vertical'
align: The alignment of legend items in 'horizontal' direction, which can be 'left', 'center', 'right'.
vertical_align: The alignment of legend items in 'vertical' direction, which can be 'top', 'middle', 'bottom'.
icon_size: The size of icon in each legend item.
layout: The layout of legend items. 'horizontal' | 'vertical'. Default: "horizontal"
align: The alignment of legend items in 'horizontal' direction, which can be 'left', 'center', 'right'. Default: "center"
vertical_align: The alignment of legend items in 'vertical' direction, which can be 'top', 'middle', 'bottom'. Default: "bottom"
icon_size: The size of icon in each legend item. Default: 14
icon_type: The type of icon in each legend item. 'line' | 'plainline' | 'square' | 'rect' | 'circle' | 'cross' | 'diamond' | 'star' | 'triangle' | 'wye'
payload: The source data of the content to be displayed in the legend, usually calculated internally. Default: []
chart_width: The width of chart container, usually calculated internally.
chart_height: The height of chart container, usually calculated internally.
margin: The margin of chart container, usually calculated internally.
@ -553,10 +558,10 @@ class LabelList(Recharts):
Args:
*children: The children of the component.
data_key: The key of a group of label values in data.
position: The position of each label relative to it view box"Top" | "left" | "right" | "bottom" | "inside" | "outside" | "insideLeft" | "insideRight" | "insideTop" | "insideBottom" | "insideTopLeft" | "insideBottomLeft" | "insideTopRight" | "insideBottomRight" | "insideStart" | "insideEnd" | "end" | "center"
offset: The offset to the specified "position"
fill: The fill color of each label
stroke: The stroke color of each label
position: The position of each label relative to it view box. "Top" | "left" | "right" | "bottom" | "inside" | "outside" | "insideLeft" | "insideRight" | "insideTop" | "insideBottom" | "insideTopLeft" | "insideBottomLeft" | "insideTopRight" | "insideBottomRight" | "insideStart" | "insideEnd" | "end" | "center"
offset: The offset to the specified "position". Default: 5
fill: The fill color of each label. Default: rx.color("gray", 10)
stroke: The stroke color of each label. Default: "none"
style: The style of the component.
key: A unique key for the component.
id: The id for the component.

View File

@ -106,36 +106,50 @@ class Radar(Recharts):
# The coordinates of all the vertexes of the radar shape, like [{ x, y }].
points: Var[List[Dict[str, Any]]]
# If false set, dots will not be drawn
# If false set, dots will not be drawn. Default: True
dot: Var[bool]
# Stoke color
# Stoke color. Default: rx.color("accent", 9)
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
# Fill color
# Fill color. Default: rx.color("accent", 3)
fill: Var[str] = LiteralVar.create(Color("accent", 3))
# opacity
# opacity. Default: 0.6
fill_opacity: Var[float] = LiteralVar.create(0.6)
# The type of icon in legend. If set to 'none', no legend item will be rendered.
legend_type: Var[str]
# The type of icon in legend. If set to 'none', no legend item will be rendered. Default: "rect"
legend_type: Var[LiteralLegendType]
# If false set, labels will not be drawn
# If false set, labels will not be drawn. Default: True
label: Var[bool]
# Specifies when the animation should begin, the unit of this option is ms.
# If set false, animation of polygon will be disabled. Default: True in CSR, and False in SSR
is_animation_active: Var[bool]
# Specifies when the animation should begin, the unit of this option is ms. Default: 0
animation_begin: Var[int]
# Specifies the duration of animation, the unit of this option is ms.
# Specifies the duration of animation, the unit of this option is ms. Default: 1500
animation_duration: Var[int]
# The type of easing function. 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear'
# The type of easing function. 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear'. Default: "ease"
animation_easing: Var[LiteralAnimationEasing]
# Valid children components
_valid_children: List[str] = ["LabelList"]
def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
"""Get the event triggers that pass the component's value to the handler.
Returns:
A dict mapping the event trigger to the var that is passed to the handler.
"""
return {
EventTriggers.ON_ANIMATION_START: lambda: [],
EventTriggers.ON_ANIMATION_END: lambda: [],
}
class RadialBar(Recharts):
"""A RadialBar chart component in Recharts."""
@ -211,28 +225,28 @@ class PolarAngleAxis(Recharts):
# The outer radius of circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy.
radius: Var[Union[int, str]]
# If false set, axis line will not be drawn. If true set, axis line will be drawn which have the props calculated internally. If object set, axis line will be drawn which have the props mergered by the internal calculated props and the option.
# If false set, axis line will not be drawn. If true set, axis line will be drawn which have the props calculated internally. If object set, axis line will be drawn which have the props mergered by the internal calculated props and the option. Default: True
axis_line: Var[Union[bool, Dict[str, Any]]]
# The type of axis line.
axis_line_type: Var[str]
# The type of axis line. Default: "polygon"
axis_line_type: Var[LiteralGridType]
# If false set, tick lines will not be drawn. If true set, tick lines will be drawn which have the props calculated internally. If object set, tick lines will be drawn which have the props mergered by the internal calculated props and the option.
# If false set, tick lines will not be drawn. If true set, tick lines will be drawn which have the props calculated internally. If object set, tick lines will be drawn which have the props mergered by the internal calculated props and the option. Default: False
tick_line: Var[Union[bool, Dict[str, Any]]] = LiteralVar.create(False)
# The width or height of tick.
tick: Var[Union[int, str]]
# If false set, ticks will not be drawn. If true set, ticks will be drawn which have the props calculated internally. If object set, ticks will be drawn which have the props mergered by the internal calculated props and the option. Default: True
tick: Var[Union[bool, Dict[str, Any]]]
# The array of every tick's value and angle.
ticks: Var[List[Dict[str, Any]]]
# The orientation of axis text.
orient: Var[str]
# The orientation of axis text. Default: "outer"
orientation: Var[str]
# The stroke color of axis
# The stroke color of axis. Default: rx.color("gray", 10)
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 10))
# Allow the axis has duplicated categorys or not when the type of axis is "category".
# Allow the axis has duplicated categorys or not when the type of axis is "category". Default: True
allow_duplicated_category: Var[bool]
# Valid children components.
@ -270,17 +284,17 @@ class PolarGrid(Recharts):
alias = "RechartsPolarGrid"
# The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of container width.
cx: Var[Union[int, str]]
# The x-coordinate of center.
cx: Var[int]
# The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of container height.
cy: Var[Union[int, str]]
# The y-coordinate of center.
cy: Var[int]
# The radius of the inner polar grid.
inner_radius: Var[Union[int, str]]
inner_radius: Var[int]
# The radius of the outer polar grid.
outer_radius: Var[Union[int, str]]
outer_radius: Var[int]
# The array of every line grid's angle.
polar_angles: Var[List[int]]
@ -288,10 +302,10 @@ class PolarGrid(Recharts):
# The array of every line grid's radius.
polar_radius: Var[List[int]]
# The type of polar grids. 'polygon' | 'circle'
# The type of polar grids. 'polygon' | 'circle'. Default: "polygon"
grid_type: Var[LiteralGridType]
# The stroke color of grid
# The stroke color of grid. Default: rx.color("gray", 10)
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 10))
# Valid children components

View File

@ -126,6 +126,7 @@ class Pie(Recharts):
...
class Radar(Recharts):
def get_event_triggers(self) -> dict[str, Union[Var, Any]]: ...
@overload
@classmethod
def create( # type: ignore
@ -137,8 +138,40 @@ class Radar(Recharts):
stroke: Optional[Union[Color, Var[Union[Color, str]], str]] = None,
fill: Optional[Union[Var[str], str]] = None,
fill_opacity: Optional[Union[Var[float], float]] = None,
legend_type: Optional[Union[Var[str], str]] = None,
legend_type: Optional[
Union[
Literal[
"circle",
"cross",
"diamond",
"line",
"none",
"plainline",
"rect",
"square",
"star",
"triangle",
"wye",
],
Var[
Literal[
"circle",
"cross",
"diamond",
"line",
"none",
"plainline",
"rect",
"square",
"star",
"triangle",
"wye",
]
],
]
] = None,
label: Optional[Union[Var[bool], bool]] = None,
is_animation_active: Optional[Union[Var[bool], bool]] = None,
animation_begin: Optional[Union[Var[int], int]] = None,
animation_duration: Optional[Union[Var[int], int]] = None,
animation_easing: Optional[
@ -153,39 +186,10 @@ class Radar(Recharts):
class_name: Optional[Any] = None,
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None,
on_click: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None,
on_context_menu: Optional[
on_animation_end: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
on_double_click: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
on_focus: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None,
on_mount: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None,
on_mouse_down: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
on_mouse_enter: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
on_mouse_leave: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
on_mouse_move: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
on_mouse_out: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
on_mouse_over: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
on_mouse_up: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
on_scroll: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None,
on_unmount: Optional[
on_animation_start: Optional[
Union[EventHandler, EventSpec, list, Callable, Var]
] = None,
**props,
@ -196,15 +200,16 @@ class Radar(Recharts):
*children: The children of the component.
data_key: The key of a group of data which should be unique in a radar chart.
points: The coordinates of all the vertexes of the radar shape, like [{ x, y }].
dot: If false set, dots will not be drawn
stroke: Stoke color
fill: Fill color
fill_opacity: opacity
legend_type: The type of icon in legend. If set to 'none', no legend item will be rendered.
label: If false set, labels will not be drawn
animation_begin: Specifies when the animation should begin, the unit of this option is ms.
animation_duration: Specifies the duration of animation, the unit of this option is ms.
animation_easing: The type of easing function. 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear'
dot: If false set, dots will not be drawn. Default: True
stroke: Stoke color. Default: rx.color("accent", 9)
fill: Fill color. Default: rx.color("accent", 3)
fill_opacity: opacity. Default: 0.6
legend_type: The type of icon in legend. If set to 'none', no legend item will be rendered. Default: "rect"
label: If false set, labels will not be drawn. Default: True
is_animation_active: If set false, animation of polygon will be disabled. Default: True in CSR, and False in SSR
animation_begin: Specifies when the animation should begin, the unit of this option is ms. Default: 0
animation_duration: Specifies the duration of animation, the unit of this option is ms. Default: 1500
animation_easing: The type of easing function. 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear'. Default: "ease"
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
@ -312,13 +317,17 @@ class PolarAngleAxis(Recharts):
axis_line: Optional[
Union[Dict[str, Any], Var[Union[Dict[str, Any], bool]], bool]
] = None,
axis_line_type: Optional[Union[Var[str], str]] = None,
axis_line_type: Optional[
Union[Literal["circle", "polygon"], Var[Literal["circle", "polygon"]]]
] = None,
tick_line: Optional[
Union[Dict[str, Any], Var[Union[Dict[str, Any], bool]], bool]
] = None,
tick: Optional[Union[Var[Union[int, str]], int, str]] = None,
tick: Optional[
Union[Dict[str, Any], Var[Union[Dict[str, Any], bool]], bool]
] = None,
ticks: Optional[Union[List[Dict[str, Any]], Var[List[Dict[str, Any]]]]] = None,
orient: Optional[Union[Var[str], str]] = None,
orientation: Optional[Union[Var[str], str]] = None,
stroke: Optional[Union[Color, Var[Union[Color, str]], str]] = None,
allow_duplicated_category: Optional[Union[Var[bool], bool]] = None,
style: Optional[Style] = None,
@ -372,14 +381,14 @@ class PolarAngleAxis(Recharts):
cx: The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of container width.
cy: The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of container height.
radius: The outer radius of circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy.
axis_line: If false set, axis line will not be drawn. If true set, axis line will be drawn which have the props calculated internally. If object set, axis line will be drawn which have the props mergered by the internal calculated props and the option.
axis_line_type: The type of axis line.
tick_line: If false set, tick lines will not be drawn. If true set, tick lines will be drawn which have the props calculated internally. If object set, tick lines will be drawn which have the props mergered by the internal calculated props and the option.
tick: The width or height of tick.
axis_line: If false set, axis line will not be drawn. If true set, axis line will be drawn which have the props calculated internally. If object set, axis line will be drawn which have the props mergered by the internal calculated props and the option. Default: True
axis_line_type: The type of axis line. Default: "polygon"
tick_line: If false set, tick lines will not be drawn. If true set, tick lines will be drawn which have the props calculated internally. If object set, tick lines will be drawn which have the props mergered by the internal calculated props and the option. Default: False
tick: If false set, ticks will not be drawn. If true set, ticks will be drawn which have the props calculated internally. If object set, ticks will be drawn which have the props mergered by the internal calculated props and the option. Default: True
ticks: The array of every tick's value and angle.
orient: The orientation of axis text.
stroke: The stroke color of axis
allow_duplicated_category: Allow the axis has duplicated categorys or not when the type of axis is "category".
orientation: The orientation of axis text. Default: "outer"
stroke: The stroke color of axis. Default: rx.color("gray", 10)
allow_duplicated_category: Allow the axis has duplicated categorys or not when the type of axis is "category". Default: True
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
@ -399,10 +408,10 @@ class PolarGrid(Recharts):
def create( # type: ignore
cls,
*children,
cx: Optional[Union[Var[Union[int, str]], int, str]] = None,
cy: Optional[Union[Var[Union[int, str]], int, str]] = None,
inner_radius: Optional[Union[Var[Union[int, str]], int, str]] = None,
outer_radius: Optional[Union[Var[Union[int, str]], int, str]] = None,
cx: Optional[Union[Var[int], int]] = None,
cy: Optional[Union[Var[int], int]] = None,
inner_radius: Optional[Union[Var[int], int]] = None,
outer_radius: Optional[Union[Var[int], int]] = None,
polar_angles: Optional[Union[List[int], Var[List[int]]]] = None,
polar_radius: Optional[Union[List[int], Var[List[int]]]] = None,
grid_type: Optional[
@ -456,14 +465,14 @@ class PolarGrid(Recharts):
Args:
*children: The children of the component.
cx: The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of container width.
cy: The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of container height.
cx: The x-coordinate of center.
cy: The y-coordinate of center.
inner_radius: The radius of the inner polar grid.
outer_radius: The radius of the outer polar grid.
polar_angles: The array of every line grid's angle.
polar_radius: The array of every line grid's radius.
grid_type: The type of polar grids. 'polygon' | 'circle'
stroke: The stroke color of grid
grid_type: The type of polar grids. 'polygon' | 'circle'. Default: "polygon"
stroke: The stroke color of grid. Default: rx.color("gray", 10)
style: The style of the component.
key: A unique key for the component.
id: The id for the component.

View File

@ -192,7 +192,7 @@ class ToastProps(PropsBase):
class Toaster(Component):
"""A Toaster Component for displaying toast notifications."""
library: str = "sonner@1.4.41"
library: str = "sonner@1.5.0"
tag = "Toaster"

View File

@ -48,10 +48,10 @@ class IterTag(Tag):
"""
iterable = self.iterable
try:
if iterable._var_type.mro()[0] == dict:
if iterable._var_type.mro()[0] is dict:
# Arg is a tuple of (key, value).
return Tuple[get_args(iterable._var_type)] # type: ignore
elif iterable._var_type.mro()[0] == tuple:
elif iterable._var_type.mro()[0] is tuple:
# Arg is a union of any possible values in the tuple.
return Union[get_args(iterable._var_type)] # type: ignore
else:

View File

@ -6,7 +6,8 @@ import importlib
import os
import sys
import urllib.parse
from typing import Any, Dict, List, Optional, Set
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Union
from reflex.utils.exceptions import ConfigError
@ -190,7 +191,7 @@ class Config(Base):
telemetry_enabled: bool = True
# The bun path
bun_path: str = constants.Bun.DEFAULT_PATH
bun_path: Union[str, Path] = constants.Bun.DEFAULT_PATH
# List of origins that are allowed to connect to the backend API.
cors_allowed_origins: List[str] = ["*"]

View File

@ -6,6 +6,7 @@ import os
import platform
from enum import Enum
from importlib import metadata
from pathlib import Path
from types import SimpleNamespace
from platformdirs import PlatformDirs
@ -66,18 +67,19 @@ class Reflex(SimpleNamespace):
# Get directory value from enviroment variables if it exists.
_dir = os.environ.get("REFLEX_DIR", "")
DIR = _dir or (
# on windows, we use C:/Users/<username>/AppData/Local/reflex.
# on macOS, we use ~/Library/Application Support/reflex.
# on linux, we use ~/.local/share/reflex.
# If user sets REFLEX_DIR envroment variable use that instead.
PlatformDirs(MODULE_NAME, False).user_data_dir
DIR = Path(
_dir
or (
# on windows, we use C:/Users/<username>/AppData/Local/reflex.
# on macOS, we use ~/Library/Application Support/reflex.
# on linux, we use ~/.local/share/reflex.
# If user sets REFLEX_DIR envroment variable use that instead.
PlatformDirs(MODULE_NAME, False).user_data_dir
)
)
# The root directory of the reflex library.
ROOT_DIR = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)
ROOT_DIR = Path(__file__).parents[2]
RELEASES_URL = f"https://api.github.com/repos/reflex-dev/templates/releases"
@ -125,11 +127,11 @@ class Templates(SimpleNamespace):
"""Folders used by the template system of Reflex."""
# The template directory used during reflex init.
BASE = os.path.join(Reflex.ROOT_DIR, Reflex.MODULE_NAME, ".templates")
BASE = Reflex.ROOT_DIR / Reflex.MODULE_NAME / ".templates"
# The web subdirectory of the template directory.
WEB_TEMPLATE = os.path.join(BASE, "web")
WEB_TEMPLATE = BASE / "web"
# The jinja template directory.
JINJA_TEMPLATE = os.path.join(BASE, "jinja")
JINJA_TEMPLATE = BASE / "jinja"
# Where the code for the templates is stored.
CODE = "code"
@ -191,6 +193,14 @@ class LogLevel(str, Enum):
levels = list(LogLevel)
return levels.index(self) <= levels.index(other)
def subprocess_level(self):
"""Return the log level for the subprocess.
Returns:
The log level for the subprocess
"""
return self if self != LogLevel.DEFAULT else LogLevel.WARNING
# Server socket configuration variables
POLLING_MAX_HTTP_BUFFER_SIZE = 1000 * 1000

View File

@ -1,6 +1,7 @@
"""Config constants."""
import os
from pathlib import Path
from types import SimpleNamespace
from reflex.constants.base import Dirs, Reflex
@ -17,9 +18,7 @@ class Config(SimpleNamespace):
# The name of the reflex config module.
MODULE = "rxconfig"
# The python config file.
FILE = f"{MODULE}{Ext.PY}"
# The previous config file.
PREVIOUS_FILE = f"pcconfig{Ext.PY}"
FILE = Path(f"{MODULE}{Ext.PY}")
class Expiration(SimpleNamespace):
@ -37,7 +36,7 @@ class GitIgnore(SimpleNamespace):
"""Gitignore constants."""
# The gitignore file.
FILE = ".gitignore"
FILE = Path(".gitignore")
# Files to gitignore.
DEFAULTS = {Dirs.WEB, "*.db", "__pycache__/", "*.py[cod]", "assets/external/"}

View File

@ -2,6 +2,7 @@
from __future__ import annotations
from pathlib import Path
from types import SimpleNamespace
@ -11,9 +12,9 @@ class CustomComponents(SimpleNamespace):
# The name of the custom components source directory.
SRC_DIR = "custom_components"
# The name of the custom components pyproject.toml file.
PYPROJECT_TOML = "pyproject.toml"
PYPROJECT_TOML = Path("pyproject.toml")
# The name of the custom components package README file.
PACKAGE_README = "README.md"
PACKAGE_README = Path("README.md")
# The name of the custom components package .gitignore file.
PACKAGE_GITIGNORE = ".gitignore"
# The name of the distribution directory as result of a build.
@ -29,6 +30,6 @@ class CustomComponents(SimpleNamespace):
"testpypi": "https://test.pypi.org/legacy/",
}
# The .gitignore file for the custom component project.
FILE = ".gitignore"
FILE = Path(".gitignore")
# Files to gitignore.
DEFAULTS = {"__pycache__/", "*.py[cod]", "*.egg-info/", "dist/"}

View File

@ -2,7 +2,6 @@
from __future__ import annotations
import os
import platform
from types import SimpleNamespace
@ -36,15 +35,14 @@ class Bun(SimpleNamespace):
"""Bun constants."""
# The Bun version.
VERSION = "1.1.10"
VERSION = "1.1.29"
# Min Bun Version
MIN_VERSION = "0.7.0"
# The directory to store the bun.
ROOT_PATH = os.path.join(Reflex.DIR, "bun")
ROOT_PATH = Reflex.DIR / "bun"
# Default bun path.
DEFAULT_PATH = os.path.join(
ROOT_PATH, "bin", "bun" if not IS_WINDOWS else "bun.exe"
)
DEFAULT_PATH = ROOT_PATH / "bin" / ("bun" if not IS_WINDOWS else "bun.exe")
# URL to bun install script.
INSTALL_URL = "https://bun.sh/install"
# URL to windows install script.
@ -65,10 +63,10 @@ class Fnm(SimpleNamespace):
# The FNM version.
VERSION = "1.35.1"
# The directory to store fnm.
DIR = os.path.join(Reflex.DIR, "fnm")
DIR = Reflex.DIR / "fnm"
FILENAME = get_fnm_name()
# The fnm executable binary.
EXE = os.path.join(DIR, "fnm.exe" if IS_WINDOWS else "fnm")
EXE = DIR / ("fnm.exe" if IS_WINDOWS else "fnm")
# The URL to the fnm release binary
INSTALL_URL = (
@ -81,23 +79,24 @@ class Node(SimpleNamespace):
"""Node/ NPM constants."""
# The Node version.
VERSION = "18.17.0"
VERSION = "20.18.0"
# The minimum required node version.
MIN_VERSION = "18.17.0"
# The node bin path.
BIN_PATH = os.path.join(
Fnm.DIR,
"node-versions",
f"v{VERSION}",
"installation",
"bin" if not IS_WINDOWS else "",
BIN_PATH = (
Fnm.DIR
/ "node-versions"
/ f"v{VERSION}"
/ "installation"
/ ("bin" if not IS_WINDOWS else "")
)
# The default path where node is installed.
PATH = os.path.join(BIN_PATH, "node.exe" if IS_WINDOWS else "node")
PATH = BIN_PATH / ("node.exe" if IS_WINDOWS else "node")
# The default path where npm is installed.
NPM_PATH = os.path.join(BIN_PATH, "npm")
NPM_PATH = BIN_PATH / "npm"
# The environment variable to use the system installed node.
USE_SYSTEM_VAR = "REFLEX_USE_SYSTEM_NODE"
@ -117,21 +116,21 @@ class PackageJson(SimpleNamespace):
PATH = "package.json"
DEPENDENCIES = {
"@babel/standalone": "7.25.3",
"@emotion/react": "11.11.1",
"axios": "1.6.0",
"@babel/standalone": "7.25.7",
"@emotion/react": "11.13.3",
"axios": "1.7.7",
"json5": "2.2.3",
"next": "14.2.13",
"next-sitemap": "4.1.8",
"next-themes": "0.2.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-focus-lock": "2.11.3",
"socket.io-client": "4.6.1",
"universal-cookie": "4.0.4",
"next": "14.2.14",
"next-sitemap": "4.2.3",
"next-themes": "0.3.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-focus-lock": "2.13.2",
"socket.io-client": "4.8.0",
"universal-cookie": "7.2.0",
}
DEV_DEPENDENCIES = {
"autoprefixer": "10.4.14",
"postcss": "8.4.31",
"autoprefixer": "10.4.20",
"postcss": "8.4.47",
"postcss-import": "16.1.0",
}

View File

@ -7,7 +7,7 @@ class Tailwind(SimpleNamespace):
"""Tailwind constants."""
# The Tailwindcss version
VERSION = "tailwindcss@3.3.2"
VERSION = "tailwindcss@3.4.13"
# The Tailwind config.
CONFIG = "tailwind.config.js"
# Default Tailwind content paths

View File

@ -36,7 +36,7 @@ POST_CUSTOM_COMPONENTS_GALLERY_TIMEOUT = 15
@contextmanager
def set_directory(working_directory: str):
def set_directory(working_directory: str | Path):
"""Context manager that sets the working directory.
Args:
@ -45,7 +45,8 @@ def set_directory(working_directory: str):
Yields:
Yield to the caller to perform operations in the working directory.
"""
current_directory = os.getcwd()
current_directory = Path.cwd()
working_directory = Path(working_directory)
try:
os.chdir(working_directory)
yield
@ -62,14 +63,14 @@ def _create_package_config(module_name: str, package_name: str):
"""
from reflex.compiler import templates
with open(CustomComponents.PYPROJECT_TOML, "w") as f:
f.write(
templates.CUSTOM_COMPONENTS_PYPROJECT_TOML.render(
module_name=module_name,
package_name=package_name,
reflex_version=constants.Reflex.VERSION,
)
pyproject = Path(CustomComponents.PYPROJECT_TOML)
pyproject.write_text(
templates.CUSTOM_COMPONENTS_PYPROJECT_TOML.render(
module_name=module_name,
package_name=package_name,
reflex_version=constants.Reflex.VERSION,
)
)
def _get_package_config(exit_on_fail: bool = True) -> dict:
@ -84,11 +85,11 @@ def _get_package_config(exit_on_fail: bool = True) -> dict:
Raises:
Exit: If the pyproject.toml file is not found.
"""
pyproject = Path(CustomComponents.PYPROJECT_TOML)
try:
with open(CustomComponents.PYPROJECT_TOML, "rb") as f:
return dict(tomlkit.load(f))
return dict(tomlkit.loads(pyproject.read_bytes()))
except (OSError, TOMLKitError) as ex:
console.error(f"Unable to read from pyproject.toml due to {ex}")
console.error(f"Unable to read from {pyproject} due to {ex}")
if exit_on_fail:
raise typer.Exit(code=1) from ex
raise
@ -103,17 +104,17 @@ def _create_readme(module_name: str, package_name: str):
"""
from reflex.compiler import templates
with open(CustomComponents.PACKAGE_README, "w") as f:
f.write(
templates.CUSTOM_COMPONENTS_README.render(
module_name=module_name,
package_name=package_name,
)
readme = Path(CustomComponents.PACKAGE_README)
readme.write_text(
templates.CUSTOM_COMPONENTS_README.render(
module_name=module_name,
package_name=package_name,
)
)
def _write_source_and_init_py(
custom_component_src_dir: str,
custom_component_src_dir: Path,
component_class_name: str,
module_name: str,
):
@ -126,27 +127,17 @@ def _write_source_and_init_py(
"""
from reflex.compiler import templates
with open(
os.path.join(
custom_component_src_dir,
f"{module_name}.py",
),
"w",
) as f:
f.write(
templates.CUSTOM_COMPONENTS_SOURCE.render(
component_class_name=component_class_name, module_name=module_name
)
module_path = custom_component_src_dir / f"{module_name}.py"
module_path.write_text(
templates.CUSTOM_COMPONENTS_SOURCE.render(
component_class_name=component_class_name, module_name=module_name
)
)
with open(
os.path.join(
custom_component_src_dir,
CustomComponents.INIT_FILE,
),
"w",
) as f:
f.write(templates.CUSTOM_COMPONENTS_INIT_FILE.render(module_name=module_name))
init_path = custom_component_src_dir / CustomComponents.INIT_FILE
init_path.write_text(
templates.CUSTOM_COMPONENTS_INIT_FILE.render(module_name=module_name)
)
def _populate_demo_app(name_variants: NameVariants):
@ -192,7 +183,7 @@ def _get_default_library_name_parts() -> list[str]:
Returns:
The parts of default library name.
"""
current_dir_name = os.getcwd().split(os.path.sep)[-1]
current_dir_name = Path.cwd().name
cleaned_dir_name = re.sub("[^0-9a-zA-Z-_]+", "", current_dir_name).lower()
parts = [part for part in re.split("-|_", cleaned_dir_name) if part]
@ -269,7 +260,7 @@ def _validate_library_name(library_name: str | None) -> NameVariants:
# Module name is the snake case.
module_name = "_".join(name_parts)
custom_component_module_dir = f"reflex_{module_name}"
custom_component_module_dir = Path(f"reflex_{module_name}")
console.debug(f"Custom component source directory: {custom_component_module_dir}")
# Use the same name for the directory and the app.
@ -345,7 +336,7 @@ def init(
console.set_log_level(loglevel)
if os.path.exists(CustomComponents.PYPROJECT_TOML):
if CustomComponents.PYPROJECT_TOML.exists():
console.error(f"A {CustomComponents.PYPROJECT_TOML} already exists. Aborting.")
typer.Exit(code=1)

View File

@ -4,16 +4,19 @@ from __future__ import annotations
import dataclasses
import inspect
import sys
import types
import urllib.parse
from base64 import b64encode
from typing import (
Any,
Callable,
ClassVar,
Dict,
List,
Optional,
Tuple,
Type,
Union,
get_type_hints,
)
@ -25,8 +28,15 @@ from reflex.utils import format
from reflex.utils.exceptions import EventFnArgMismatch, EventHandlerArgMismatch
from reflex.utils.types import ArgsSpec, GenericType
from reflex.vars import VarData
from reflex.vars.base import LiteralVar, Var
from reflex.vars.function import FunctionStringVar, FunctionVar
from reflex.vars.base import (
CachedVarOperation,
LiteralNoneVar,
LiteralVar,
ToOperation,
Var,
cached_property_no_lock,
)
from reflex.vars.function import ArgsFunctionOperation, FunctionStringVar, FunctionVar
from reflex.vars.object import ObjectVar
try:
@ -375,7 +385,7 @@ class CallableEventSpec(EventSpec):
class EventChain(EventActionsMixin):
"""Container for a chain of events that will be executed in order."""
events: List[EventSpec] = dataclasses.field(default_factory=list)
events: List[Union[EventSpec, EventVar]] = dataclasses.field(default_factory=list)
args_spec: Optional[Callable] = dataclasses.field(default=None)
@ -478,7 +488,7 @@ class FileUpload:
if isinstance(events, Var):
raise ValueError(f"{on_upload_progress} cannot return a var {events}.")
on_upload_progress_chain = EventChain(
events=events,
events=[*events],
args_spec=self.on_upload_progress_args_spec,
)
formatted_chain = str(format.format_prop(on_upload_progress_chain))
@ -839,6 +849,16 @@ def call_script(
),
),
}
if isinstance(javascript_code, str):
# When there is VarData, include it and eval the JS code inline on the client.
javascript_code, original_code = (
LiteralVar.create(javascript_code),
javascript_code,
)
if not javascript_code._get_all_var_data():
# Without VarData, cast to string and eval the code in the event loop.
javascript_code = str(Var(_js_expr=original_code))
return server_side(
"_call_script",
get_fn_signature(call_script),
@ -1126,3 +1146,178 @@ def get_fn_signature(fn: Callable) -> inspect.Signature:
"state", inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=Any
)
return signature.replace(parameters=(new_param, *signature.parameters.values()))
class EventVar(ObjectVar):
"""Base class for event vars."""
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class LiteralEventVar(CachedVarOperation, LiteralVar, EventVar):
"""A literal event var."""
_var_value: EventSpec = dataclasses.field(default=None) # type: ignore
def __hash__(self) -> int:
"""Get the hash of the var.
Returns:
The hash of the var.
"""
return hash((self.__class__.__name__, self._js_expr))
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the var.
Returns:
The name of the var.
"""
return str(
FunctionStringVar("Event").call(
# event handler name
".".join(
filter(
None,
format.get_event_handler_parts(self._var_value.handler),
)
),
# event handler args
{str(name): value for name, value in self._var_value.args},
# event actions
self._var_value.event_actions,
# client handler name
*(
[self._var_value.client_handler_name]
if self._var_value.client_handler_name
else []
),
)
)
@classmethod
def create(
cls,
value: EventSpec,
_var_data: VarData | None = None,
) -> LiteralEventVar:
"""Create a new LiteralEventVar instance.
Args:
value: The value of the var.
_var_data: The data of the var.
Returns:
The created LiteralEventVar instance.
"""
return cls(
_js_expr="",
_var_type=EventSpec,
_var_data=_var_data,
_var_value=value,
)
class EventChainVar(FunctionVar):
"""Base class for event chain vars."""
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class LiteralEventChainVar(CachedVarOperation, LiteralVar, EventChainVar):
"""A literal event chain var."""
_var_value: EventChain = dataclasses.field(default=None) # type: ignore
def __hash__(self) -> int:
"""Get the hash of the var.
Returns:
The hash of the var.
"""
return hash((self.__class__.__name__, self._js_expr))
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the var.
Returns:
The name of the var.
"""
sig = inspect.signature(self._var_value.args_spec) # type: ignore
if sig.parameters:
arg_def = tuple((f"_{p}" for p in sig.parameters))
arg_def_expr = LiteralVar.create([Var(_js_expr=arg) for arg in arg_def])
else:
# add a default argument for addEvents if none were specified in value.args_spec
# used to trigger the preventDefault() on the event.
arg_def = ("...args",)
arg_def_expr = Var(_js_expr="args")
return str(
ArgsFunctionOperation.create(
arg_def,
FunctionStringVar.create("addEvents").call(
LiteralVar.create(
[LiteralVar.create(event) for event in self._var_value.events]
),
arg_def_expr,
self._var_value.event_actions,
),
)
)
@classmethod
def create(
cls,
value: EventChain,
_var_data: VarData | None = None,
) -> LiteralEventChainVar:
"""Create a new LiteralEventChainVar instance.
Args:
value: The value of the var.
_var_data: The data of the var.
Returns:
The created LiteralEventChainVar instance.
"""
return cls(
_js_expr="",
_var_type=EventChain,
_var_data=_var_data,
_var_value=value,
)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ToEventVarOperation(ToOperation, EventVar):
"""Result of a cast to an event var."""
_original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
_default_var_type: ClassVar[Type] = EventSpec
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ToEventChainVarOperation(ToOperation, EventChainVar):
"""Result of a cast to an event chain var."""
_original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
_default_var_type: ClassVar[Type] = EventChain

126
reflex/istate/data.py Normal file
View File

@ -0,0 +1,126 @@
"""This module contains the dataclasses representing the router object."""
import dataclasses
from typing import Optional
from reflex import constants
from reflex.utils import format
@dataclasses.dataclass(frozen=True)
class HeaderData:
"""An object containing headers data."""
host: str = ""
origin: str = ""
upgrade: str = ""
connection: str = ""
cookie: str = ""
pragma: str = ""
cache_control: str = ""
user_agent: str = ""
sec_websocket_version: str = ""
sec_websocket_key: str = ""
sec_websocket_extensions: str = ""
accept_encoding: str = ""
accept_language: str = ""
def __init__(self, router_data: Optional[dict] = None):
"""Initalize the HeaderData object based on router_data.
Args:
router_data: the router_data dict.
"""
if router_data:
for k, v in router_data.get(constants.RouteVar.HEADERS, {}).items():
object.__setattr__(self, format.to_snake_case(k), v)
else:
for k in dataclasses.fields(self):
object.__setattr__(self, k.name, "")
@dataclasses.dataclass(frozen=True)
class PageData:
"""An object containing page data."""
host: str = "" # repeated with self.headers.origin (remove or keep the duplicate?)
path: str = ""
raw_path: str = ""
full_path: str = ""
full_raw_path: str = ""
params: dict = dataclasses.field(default_factory=dict)
def __init__(self, router_data: Optional[dict] = None):
"""Initalize the PageData object based on router_data.
Args:
router_data: the router_data dict.
"""
if router_data:
object.__setattr__(
self,
"host",
router_data.get(constants.RouteVar.HEADERS, {}).get("origin", ""),
)
object.__setattr__(
self, "path", router_data.get(constants.RouteVar.PATH, "")
)
object.__setattr__(
self, "raw_path", router_data.get(constants.RouteVar.ORIGIN, "")
)
object.__setattr__(self, "full_path", f"{self.host}{self.path}")
object.__setattr__(self, "full_raw_path", f"{self.host}{self.raw_path}")
object.__setattr__(
self, "params", router_data.get(constants.RouteVar.QUERY, {})
)
else:
object.__setattr__(self, "host", "")
object.__setattr__(self, "path", "")
object.__setattr__(self, "raw_path", "")
object.__setattr__(self, "full_path", "")
object.__setattr__(self, "full_raw_path", "")
object.__setattr__(self, "params", {})
@dataclasses.dataclass(frozen=True, init=False)
class SessionData:
"""An object containing session data."""
client_token: str = ""
client_ip: str = ""
session_id: str = ""
def __init__(self, router_data: Optional[dict] = None):
"""Initalize the SessionData object based on router_data.
Args:
router_data: the router_data dict.
"""
if router_data:
client_token = router_data.get(constants.RouteVar.CLIENT_TOKEN, "")
client_ip = router_data.get(constants.RouteVar.CLIENT_IP, "")
session_id = router_data.get(constants.RouteVar.SESSION_ID, "")
else:
client_token = client_ip = session_id = ""
object.__setattr__(self, "client_token", client_token)
object.__setattr__(self, "client_ip", client_ip)
object.__setattr__(self, "session_id", session_id)
@dataclasses.dataclass(frozen=True, init=False)
class RouterData:
"""An object containing RouterData."""
session: SessionData = dataclasses.field(default_factory=SessionData)
headers: HeaderData = dataclasses.field(default_factory=HeaderData)
page: PageData = dataclasses.field(default_factory=PageData)
def __init__(self, router_data: Optional[dict] = None):
"""Initialize the RouterData object.
Args:
router_data: the router_data dict.
"""
object.__setattr__(self, "session", SessionData(router_data))
object.__setattr__(self, "headers", HeaderData(router_data))
object.__setattr__(self, "page", PageData(router_data))

View File

@ -15,7 +15,6 @@ from reflex_cli.utils import dependency
from reflex import constants
from reflex.config import get_config
from reflex.constants.base import LogLevel
from reflex.custom_components.custom_components import custom_components_cli
from reflex.state import reset_disk_state_manager
from reflex.utils import console, redir, telemetry
@ -115,9 +114,6 @@ def _init(
app_name, generation_hash=generation_hash
)
# Migrate Pynecone projects to Reflex.
prerequisites.migrate_to_reflex()
# Initialize the .gitignore.
prerequisites.initialize_gitignore()
@ -247,11 +243,6 @@ def _run(
setup_frontend(Path.cwd())
commands.append((frontend_cmd, Path.cwd(), frontend_port, backend))
# If no loglevel is specified, set the subprocesses loglevel to WARNING.
subprocesses_loglevel = (
loglevel if loglevel != LogLevel.DEFAULT else LogLevel.WARNING
)
# In prod mode, run the backend on a separate thread.
if backend and env == constants.Env.PROD:
commands.append(
@ -259,7 +250,7 @@ def _run(
backend_cmd,
backend_host,
backend_port,
subprocesses_loglevel,
loglevel.subprocess_level(),
frontend,
)
)
@ -269,7 +260,7 @@ def _run(
# In dev mode, run the backend on the main thread.
if backend and env == constants.Env.DEV:
backend_cmd(
backend_host, int(backend_port), subprocesses_loglevel, frontend
backend_host, int(backend_port), loglevel.subprocess_level(), frontend
)
# The windows uvicorn bug workaround
# https://github.com/reflex-dev/reflex/issues/2335
@ -342,7 +333,7 @@ def export(
backend=backend,
zip_dest_dir=zip_dest_dir,
upload_db_file=upload_db_file,
loglevel=loglevel,
loglevel=loglevel.subprocess_level(),
)
@ -577,7 +568,7 @@ def deploy(
frontend=frontend,
backend=backend,
zipping=zipping,
loglevel=loglevel,
loglevel=loglevel.subprocess_level(),
upload_db_file=upload_db_file,
),
key=key,
@ -591,7 +582,7 @@ def deploy(
interactive=interactive,
with_metrics=with_metrics,
with_tracing=with_tracing,
loglevel=loglevel.value,
loglevel=loglevel.subprocess_level(),
)

View File

@ -9,6 +9,7 @@ import dataclasses
import functools
import inspect
import os
import pickle
import uuid
from abc import ABC, abstractmethod
from collections import defaultdict
@ -19,6 +20,7 @@ from typing import (
TYPE_CHECKING,
Any,
AsyncIterator,
BinaryIO,
Callable,
ClassVar,
Dict,
@ -30,14 +32,15 @@ from typing import (
Type,
Union,
cast,
get_args,
get_type_hints,
)
import dill
from sqlalchemy.orm import DeclarativeBase
from typing_extensions import Self
from reflex.config import get_config
from reflex.istate.data import RouterData
from reflex.vars.base import (
ComputedVar,
DynamicRouteVar,
@ -75,10 +78,12 @@ from reflex.utils.exceptions import (
ImmutableStateError,
InvalidStateManagerMode,
LockExpiredError,
SetUndefinedStateVarError,
StateSchemaMismatchError,
)
from reflex.utils.exec import is_testing_env
from reflex.utils.serializers import serializer
from reflex.utils.types import override
from reflex.utils.types import get_origin, override
from reflex.vars import VarData
if TYPE_CHECKING:
@ -93,125 +98,6 @@ var = computed_var
TOO_LARGE_SERIALIZED_STATE = 100 * 1024 # 100kb
@dataclasses.dataclass(frozen=True)
class HeaderData:
"""An object containing headers data."""
host: str = ""
origin: str = ""
upgrade: str = ""
connection: str = ""
cookie: str = ""
pragma: str = ""
cache_control: str = ""
user_agent: str = ""
sec_websocket_version: str = ""
sec_websocket_key: str = ""
sec_websocket_extensions: str = ""
accept_encoding: str = ""
accept_language: str = ""
def __init__(self, router_data: Optional[dict] = None):
"""Initalize the HeaderData object based on router_data.
Args:
router_data: the router_data dict.
"""
if router_data:
for k, v in router_data.get(constants.RouteVar.HEADERS, {}).items():
object.__setattr__(self, format.to_snake_case(k), v)
else:
for k in dataclasses.fields(self):
object.__setattr__(self, k.name, "")
@dataclasses.dataclass(frozen=True)
class PageData:
"""An object containing page data."""
host: str = "" # repeated with self.headers.origin (remove or keep the duplicate?)
path: str = ""
raw_path: str = ""
full_path: str = ""
full_raw_path: str = ""
params: dict = dataclasses.field(default_factory=dict)
def __init__(self, router_data: Optional[dict] = None):
"""Initalize the PageData object based on router_data.
Args:
router_data: the router_data dict.
"""
if router_data:
object.__setattr__(
self,
"host",
router_data.get(constants.RouteVar.HEADERS, {}).get("origin", ""),
)
object.__setattr__(
self, "path", router_data.get(constants.RouteVar.PATH, "")
)
object.__setattr__(
self, "raw_path", router_data.get(constants.RouteVar.ORIGIN, "")
)
object.__setattr__(self, "full_path", f"{self.host}{self.path}")
object.__setattr__(self, "full_raw_path", f"{self.host}{self.raw_path}")
object.__setattr__(
self, "params", router_data.get(constants.RouteVar.QUERY, {})
)
else:
object.__setattr__(self, "host", "")
object.__setattr__(self, "path", "")
object.__setattr__(self, "raw_path", "")
object.__setattr__(self, "full_path", "")
object.__setattr__(self, "full_raw_path", "")
object.__setattr__(self, "params", {})
@dataclasses.dataclass(frozen=True, init=False)
class SessionData:
"""An object containing session data."""
client_token: str = ""
client_ip: str = ""
session_id: str = ""
def __init__(self, router_data: Optional[dict] = None):
"""Initalize the SessionData object based on router_data.
Args:
router_data: the router_data dict.
"""
if router_data:
client_token = router_data.get(constants.RouteVar.CLIENT_TOKEN, "")
client_ip = router_data.get(constants.RouteVar.CLIENT_IP, "")
session_id = router_data.get(constants.RouteVar.SESSION_ID, "")
else:
client_token = client_ip = session_id = ""
object.__setattr__(self, "client_token", client_token)
object.__setattr__(self, "client_ip", client_ip)
object.__setattr__(self, "session_id", session_id)
@dataclasses.dataclass(frozen=True, init=False)
class RouterData:
"""An object containing RouterData."""
session: SessionData = dataclasses.field(default_factory=SessionData)
headers: HeaderData = dataclasses.field(default_factory=HeaderData)
page: PageData = dataclasses.field(default_factory=PageData)
def __init__(self, router_data: Optional[dict] = None):
"""Initialize the RouterData object.
Args:
router_data: the router_data dict.
"""
object.__setattr__(self, "session", SessionData(router_data))
object.__setattr__(self, "headers", HeaderData(router_data))
object.__setattr__(self, "page", PageData(router_data))
def _no_chain_background_task(
state_cls: Type["BaseState"], name: str, fn: Callable
) -> Callable:
@ -356,12 +242,16 @@ def get_var_for_field(cls: Type[BaseState], f: ModelField):
Returns:
The Var instance.
"""
from reflex.vars import Field
field_name = format.format_state_name(cls.get_full_name()) + "." + f.name
return dispatch(
field_name=field_name,
var_data=VarData.from_state(cls, f.name),
result_var_type=f.outer_type_,
result_var_type=f.outer_type_
if get_origin(f.outer_type_) is not Field
else get_args(f.outer_type_)[0],
)
@ -577,10 +467,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
for name, value in cls.__dict__.items()
if types.is_backend_base_variable(name, cls)
}
# Add annotated backend vars that do not have a default value.
# Add annotated backend vars that may not have a default value.
new_backend_vars.update(
{
name: Var("", _var_type=annotation_value).get_default_value()
name: cls._get_var_default(name, annotation_value)
for name, annotation_value in get_type_hints(cls).items()
if name not in new_backend_vars
and types.is_backend_base_variable(name, cls)
@ -699,11 +589,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
)
@classmethod
def _evaluate(cls, f: Callable[[Self], Any]) -> Var:
def _evaluate(
cls, f: Callable[[Self], Any], of_type: Union[type, None] = None
) -> Var:
"""Evaluate a function to a ComputedVar. Experimental.
Args:
f: The function to evaluate.
of_type: The type of the ComputedVar. Defaults to Component.
Returns:
The ComputedVar.
@ -711,14 +604,23 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
console.warn(
"The _evaluate method is experimental and may be removed in future versions."
)
from reflex.components.base.fragment import fragment
from reflex.components.component import Component
of_type = of_type or Component
unique_var_name = get_unique_variable_name()
@computed_var(_js_expr=unique_var_name, return_type=Component)
@computed_var(_js_expr=unique_var_name, return_type=of_type)
def computed_var_func(state: Self):
return fragment(f(state))
result = f(state)
if not isinstance(result, of_type):
console.warn(
f"Inline ComputedVar {f} expected type {of_type}, got {type(result)}. "
"You can specify expected type with `of_type` argument."
)
return result
setattr(cls, unique_var_name, computed_var_func)
cls.computed_vars[unique_var_name] = computed_var_func
@ -795,6 +697,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
parent_state.get_parent_state(),
)
# Reset cached schema value
cls._to_schema.cache_clear()
@classmethod
def _check_overridden_methods(cls):
"""Check for shadow methods and raise error if any.
@ -1094,6 +999,26 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
# Ensure frontend uses null coalescing when accessing.
object.__setattr__(prop, "_var_type", Optional[prop._var_type])
@classmethod
def _get_var_default(cls, name: str, annotation_value: Any) -> Any:
"""Get the default value of a (backend) var.
Args:
name: The name of the var.
annotation_value: The annotation value of the var.
Returns:
The default value of the var or None.
"""
try:
return getattr(cls, name)
except AttributeError:
try:
return Var("", _var_type=annotation_value).get_default_value()
except TypeError:
pass
return None
@staticmethod
def _get_base_functions() -> dict[str, FunctionType]:
"""Get all functions of the state class excluding dunder methods.
@ -1261,6 +1186,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
Args:
name: The name of the attribute.
value: The value of the attribute.
Raises:
SetUndefinedStateVarError: If a value of a var is set without first defining it.
"""
if isinstance(value, MutableProxy):
# unwrap proxy objects when assigning back to the state
@ -1278,6 +1206,17 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
self._mark_dirty()
return
if (
name not in self.vars
and name not in self.get_skip_vars()
and not name.startswith("__")
and not name.startswith(f"_{type(self).__name__}__")
):
raise SetUndefinedStateVarError(
f"The state variable '{name}' has not been defined in '{type(self).__name__}'. "
f"All state variables must be declared before they can be set."
)
# Set the attribute.
super().__setattr__(name, value)
@ -1305,6 +1244,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
default = copy.deepcopy(field.default)
setattr(self, prop_name, default)
# Reset the backend vars.
for prop_name, value in self.backend_vars.items():
setattr(self, prop_name, copy.deepcopy(value))
# Recursively reset the substates.
for substate in self.substates.values():
substate.reset()
@ -2006,7 +1949,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
def __getstate__(self):
"""Get the state for redis serialization.
This method is called by cloudpickle to serialize the object.
This method is called by pickle to serialize the object.
It explicitly removes parent_state and substates because those are serialized separately
by the StateManagerRedis to allow for better horizontal scaling as state size increases.
@ -2015,11 +1958,93 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
The state dict for serialization.
"""
state = super().__getstate__()
# Never serialize parent_state or substates
state["__dict__"] = state["__dict__"].copy()
if state["__dict__"].get("parent_state") is not None:
# Do not serialize router data in substates (only the root state).
state["__dict__"].pop("router", None)
state["__dict__"].pop("router_data", None)
# Never serialize parent_state or substates.
state["__dict__"]["parent_state"] = None
state["__dict__"]["substates"] = {}
state["__dict__"].pop("_was_touched", None)
# Remove all inherited vars.
for inherited_var_name in self.inherited_vars:
state["__dict__"].pop(inherited_var_name, None)
return state
@classmethod
@functools.lru_cache()
def _to_schema(cls) -> str:
"""Convert a state to a schema.
Returns:
The hash of the schema.
"""
def _field_tuple(
field_name: str,
) -> Tuple[str, str, Any, Union[bool, None], Any]:
model_field = cls.__fields__[field_name]
return (
field_name,
model_field.name,
_serialize_type(model_field.type_),
(
model_field.required
if isinstance(model_field.required, bool)
else None
),
(model_field.default if is_serializable(model_field.default) else None),
)
return md5(
pickle.dumps(
list(sorted(_field_tuple(field_name) for field_name in cls.base_vars))
)
).hexdigest()
def _serialize(self) -> bytes:
"""Serialize the state for redis.
Returns:
The serialized state.
"""
try:
return pickle.dumps((self._to_schema(), self))
except pickle.PicklingError:
console.warn(
f"Failed to serialize state {self.get_full_name()} due to unpicklable object. "
"This state will not be persisted."
)
return b""
@classmethod
def _deserialize(
cls, data: bytes | None = None, fp: BinaryIO | None = None
) -> BaseState:
"""Deserialize the state from redis/disk.
data and fp are mutually exclusive, but one must be provided.
Args:
data: The serialized state data.
fp: The file pointer to the serialized state data.
Returns:
The deserialized state.
Raises:
ValueError: If both data and fp are provided, or neither are provided.
StateSchemaMismatchError: If the state schema does not match the expected schema.
"""
if data is not None and fp is None:
(substate_schema, state) = pickle.loads(data)
elif fp is not None and data is None:
(substate_schema, state) = pickle.load(fp)
else:
raise ValueError("Only one of `data` or `fp` must be provided")
if substate_schema != state._to_schema():
raise StateSchemaMismatchError()
return state
@ -2178,7 +2203,11 @@ class ComponentState(State, mixin=True):
"""
cls._per_component_state_instance_count += 1
state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}"
component_state = type(state_cls_name, (cls, State), {}, mixin=False)
component_state = type(
state_cls_name, (cls, State), {"__module__": __name__}, mixin=False
)
# Save a reference to the dynamic state for pickle/unpickle.
globals()[state_cls_name] = component_state
component = component_state.get_component(*children, **props)
component.State = component_state
return component
@ -2654,40 +2683,11 @@ def is_serializable(value: Any) -> bool:
Whether the value is serializable.
"""
try:
return bool(dill.dumps(value))
return bool(pickle.dumps(value))
except Exception:
return False
def state_to_schema(
state: BaseState,
) -> List[Tuple[str, str, Any, Union[bool, None], Any]]:
"""Convert a state to a schema.
Args:
state: The state to convert to a schema.
Returns:
The schema.
"""
return list(
sorted(
(
field_name,
model_field.name,
_serialize_type(model_field.type_),
(
model_field.required
if isinstance(model_field.required, bool)
else None
),
(model_field.default if is_serializable(model_field.default) else None),
)
for field_name, model_field in state.__fields__.items()
)
)
def reset_disk_state_manager():
"""Reset the disk state manager."""
states_directory = prerequisites.get_web_dir() / constants.Dirs.STATES
@ -2770,35 +2770,24 @@ class StateManagerDisk(StateManager):
self.states_directory / f"{md5(token.encode()).hexdigest()}.pkl"
).absolute()
async def load_state(self, token: str, root_state: BaseState) -> BaseState:
async def load_state(self, token: str) -> BaseState | None:
"""Load a state object based on the provided token.
Args:
token: The token used to identify the state object.
root_state: The root state object.
Returns:
The loaded state object.
The loaded state object or None.
"""
if token in self.states:
return self.states[token]
client_token, substate_address = _split_substate_key(token)
token_path = self.token_path(token)
if token_path.exists():
try:
with token_path.open(mode="rb") as file:
(substate_schema, substate) = dill.load(file)
if substate_schema == state_to_schema(substate):
await self.populate_substates(client_token, substate, root_state)
return substate
return BaseState._deserialize(fp=file)
except Exception:
pass
return root_state.get_substate(substate_address.split(".")[1:])
async def populate_substates(
self, client_token: str, state: BaseState, root_state: BaseState
):
@ -2812,10 +2801,13 @@ class StateManagerDisk(StateManager):
for substate in state.get_substates():
substate_token = _substate_key(client_token, substate)
substate = await self.load_state(substate_token, root_state)
instance = await self.load_state(substate_token)
if instance is None:
instance = await root_state.get_state(substate)
state.substates[substate.get_name()] = instance
instance.parent_state = state
state.substates[substate.get_name()] = substate
substate.parent_state = state
await self.populate_substates(client_token, instance, root_state)
@override
async def get_state(
@ -2830,13 +2822,24 @@ class StateManagerDisk(StateManager):
Returns:
The state for the token.
"""
client_token, substate_address = _split_substate_key(token)
client_token = _split_substate_key(token)[0]
root_state = self.states.get(client_token)
if root_state is not None:
# Retrieved state from memory.
return root_state
root_state_token = _substate_key(client_token, substate_address.split(".")[0])
return await self.load_state(
root_state_token, self.state(_reflex_internal_init=True)
)
# Deserialize root state from disk.
root_state = await self.load_state(_substate_key(client_token, self.state))
# Create a new root state tree with all substates instantiated.
fresh_root_state = self.state(_reflex_internal_init=True)
if root_state is None:
root_state = fresh_root_state
else:
# Ensure all substates exist, even if they were not serialized previously.
root_state.substates = fresh_root_state.substates
self.states[client_token] = root_state
await self.populate_substates(client_token, root_state, root_state)
return root_state
async def set_state_for_substate(self, client_token: str, substate: BaseState):
"""Set the state for a substate.
@ -2847,12 +2850,13 @@ class StateManagerDisk(StateManager):
"""
substate_token = _substate_key(client_token, substate)
self.states[substate_token] = substate
state_dilled = dill.dumps((state_to_schema(substate), substate))
if not self.states_directory.exists():
self.states_directory.mkdir(parents=True, exist_ok=True)
self.token_path(substate_token).write_bytes(state_dilled)
if substate._get_was_touched():
substate._was_touched = False # Reset the touched flag after serializing.
pickle_state = substate._serialize()
if pickle_state:
if not self.states_directory.exists():
self.states_directory.mkdir(parents=True, exist_ok=True)
self.token_path(substate_token).write_bytes(pickle_state)
for substate_substate in substate.substates.values():
await self.set_state_for_substate(client_token, substate_substate)
@ -2892,25 +2896,6 @@ class StateManagerDisk(StateManager):
await self.set_state(token, state)
# Workaround https://github.com/cloudpipe/cloudpickle/issues/408 for dynamic pydantic classes
if not isinstance(State.validate.__func__, FunctionType):
cython_function_or_method = type(State.validate.__func__)
@dill.register(cython_function_or_method)
def _dill_reduce_cython_function_or_method(pickler, obj):
# Ignore cython function when pickling.
pass
@dill.register(type(State))
def _dill_reduce_state(pickler, obj):
if obj is not State and issubclass(obj, State):
# Avoid serializing subclasses of State, instead get them by reference from the State class.
pickler.save_reduce(State.get_class_substate, (obj.get_full_name(),), obj=obj)
else:
dill.Pickler.dispatch[type](pickler, obj)
def _default_lock_expiration() -> int:
"""Get the default lock expiration time.
@ -2951,11 +2936,14 @@ class StateManagerRedis(StateManager):
# Only warn about each state class size once.
_warned_about_state_size: ClassVar[Set[str]] = set()
async def _get_parent_state(self, token: str) -> BaseState | None:
async def _get_parent_state(
self, token: str, state: BaseState | None = None
) -> BaseState | None:
"""Get the parent state for the state requested in the token.
Args:
token: The token to get the state for (_substate_key).
state: The state instance to get parent state for.
Returns:
The parent state for the state requested by the token or None if there is no such parent.
@ -2964,11 +2952,15 @@ class StateManagerRedis(StateManager):
client_token, state_path = _split_substate_key(token)
parent_state_name = state_path.rpartition(".")[0]
if parent_state_name:
cached_substates = None
if state is not None:
cached_substates = [state]
# Retrieve the parent state to populate event handlers onto this substate.
parent_state = await self.get_state(
token=_substate_key(client_token, parent_state_name),
top_level=False,
get_substates=False,
cached_substates=cached_substates,
)
return parent_state
@ -3000,6 +2992,8 @@ class StateManagerRedis(StateManager):
tasks = {}
# Retrieve the necessary substates from redis.
for substate_cls in fetch_substates:
if substate_cls.get_name() in state.substates:
continue
substate_name = substate_cls.get_name()
tasks[substate_name] = asyncio.create_task(
self.get_state(
@ -3020,6 +3014,7 @@ class StateManagerRedis(StateManager):
top_level: bool = True,
get_substates: bool = True,
parent_state: BaseState | None = None,
cached_substates: list[BaseState] | None = None,
) -> BaseState:
"""Get the state for a token.
@ -3028,6 +3023,7 @@ class StateManagerRedis(StateManager):
top_level: If true, return an instance of the top-level state (self.state).
get_substates: If true, also retrieve substates.
parent_state: If provided, use this parent_state instead of getting it from redis.
cached_substates: If provided, attach these substates to the state.
Returns:
The state for the token.
@ -3045,45 +3041,38 @@ class StateManagerRedis(StateManager):
"StateManagerRedis requires token to be specified in the form of {token}_{state_full_name}"
)
# The deserialized or newly created (sub)state instance.
state = None
# Fetch the serialized substate from redis.
redis_state = await self.redis.get(token)
if redis_state is not None:
# Deserialize the substate.
state = dill.loads(redis_state)
# Populate parent state if missing and requested.
if parent_state is None:
parent_state = await self._get_parent_state(token)
# Set up Bidirectional linkage between this state and its parent.
if parent_state is not None:
parent_state.substates[state.get_name()] = state
state.parent_state = parent_state
# Populate substates if requested.
await self._populate_substates(token, state, all_substates=get_substates)
# To retain compatibility with previous implementation, by default, we return
# the top-level state by chasing `parent_state` pointers up the tree.
if top_level:
return state._get_root_state()
return state
# TODO: dedupe the following logic with the above block
# Key didn't exist so we have to create a new instance for this token.
with contextlib.suppress(StateSchemaMismatchError):
state = BaseState._deserialize(data=redis_state)
if state is None:
# Key didn't exist or schema mismatch so create a new instance for this token.
state = state_cls(
init_substates=False,
_reflex_internal_init=True,
)
# Populate parent state if missing and requested.
if parent_state is None:
parent_state = await self._get_parent_state(token)
# Instantiate the new state class (but don't persist it yet).
state = state_cls(
parent_state=parent_state,
init_substates=False,
_reflex_internal_init=True,
)
parent_state = await self._get_parent_state(token, state)
# Set up Bidirectional linkage between this state and its parent.
if parent_state is not None:
parent_state.substates[state.get_name()] = state
state.parent_state = parent_state
# Populate substates for the newly created state.
# Avoid fetching substates multiple times.
if cached_substates:
for substate in cached_substates:
state.substates[substate.get_name()] = substate
if substate.parent_state is None:
substate.parent_state = state
# Populate substates if requested.
await self._populate_substates(token, state, all_substates=get_substates)
# To retain compatibility with previous implementation, by default, we return
# the top-level state by chasing `parent_state` pointers up the tree.
if top_level:
@ -3162,13 +3151,14 @@ class StateManagerRedis(StateManager):
)
# Persist only the given state (parents or substates are excluded by BaseState.__getstate__).
if state._get_was_touched():
pickle_state = dill.dumps(state, byref=True)
pickle_state = state._serialize()
self._warn_if_too_large(state, len(pickle_state))
await self.redis.set(
_substate_key(client_token, state),
pickle_state,
ex=self.token_expiration,
)
if pickle_state:
await self.redis.set(
_substate_key(client_token, state),
pickle_state,
ex=self.token_expiration,
)
# Wait for substates to be persisted.
for t in tasks:

View File

@ -292,8 +292,6 @@ class AppHarness:
if isinstance(self.app_instance._state_manager, StateManagerRedis):
# Create our own redis connection for testing.
self.state_manager = StateManagerRedis.create(self.app_instance.state)
elif isinstance(self.app_instance._state_manager, StateManagerDisk):
self.state_manager = StateManagerDisk.create(self.app_instance.state)
else:
self.state_manager = self.app_instance._state_manager

View File

@ -61,8 +61,8 @@ def generate_sitemap_config(deploy_url: str, export=False):
def _zip(
component_name: constants.ComponentName,
target: str,
root_dir: str,
target: str | Path,
root_dir: str | Path,
exclude_venv_dirs: bool,
upload_db_file: bool = False,
dirs_to_exclude: set[str] | None = None,
@ -82,22 +82,22 @@ def _zip(
top_level_dirs_to_exclude: The top level directory names immediately under root_dir to exclude. Do not exclude folders by these names further in the sub-directories.
"""
target = Path(target)
root_dir = Path(root_dir)
dirs_to_exclude = dirs_to_exclude or set()
files_to_exclude = files_to_exclude or set()
files_to_zip: list[str] = []
# Traverse the root directory in a top-down manner. In this traversal order,
# we can modify the dirs list in-place to remove directories we don't want to include.
for root, dirs, files in os.walk(root_dir, topdown=True):
root = Path(root)
# Modify the dirs in-place so excluded and hidden directories are skipped in next traversal.
dirs[:] = [
d
for d in dirs
if (basename := os.path.basename(os.path.normpath(d)))
not in dirs_to_exclude
if (basename := Path(d).resolve().name) not in dirs_to_exclude
and not basename.startswith(".")
and (
not exclude_venv_dirs or not _looks_like_venv_dir(os.path.join(root, d))
)
and (not exclude_venv_dirs or not _looks_like_venv_dir(root / d))
]
# If we are at the top level with root_dir, exclude the top level dirs.
if top_level_dirs_to_exclude and root == root_dir:
@ -109,7 +109,7 @@ def _zip(
if not f.startswith(".") and (upload_db_file or not f.endswith(".db"))
]
files_to_zip += [
os.path.join(root, file) for file in files if file not in files_to_exclude
str(root / file) for file in files if file not in files_to_exclude
]
# Create a progress bar for zipping the component.
@ -126,13 +126,13 @@ def _zip(
for file in files_to_zip:
console.debug(f"{target}: {file}", progress=progress)
progress.advance(task)
zipf.write(file, os.path.relpath(file, root_dir))
zipf.write(file, Path(file).relative_to(root_dir))
def zip_app(
frontend: bool = True,
backend: bool = True,
zip_dest_dir: str = os.getcwd(),
zip_dest_dir: str | Path = Path.cwd(),
upload_db_file: bool = False,
):
"""Zip up the app.
@ -143,6 +143,7 @@ def zip_app(
zip_dest_dir: The directory to export the zip file to.
upload_db_file: Whether to upload the database file.
"""
zip_dest_dir = Path(zip_dest_dir)
files_to_exclude = {
constants.ComponentName.FRONTEND.zip(),
constants.ComponentName.BACKEND.zip(),
@ -151,8 +152,8 @@ def zip_app(
if frontend:
_zip(
component_name=constants.ComponentName.FRONTEND,
target=os.path.join(zip_dest_dir, constants.ComponentName.FRONTEND.zip()),
root_dir=str(prerequisites.get_web_dir() / constants.Dirs.STATIC),
target=zip_dest_dir / constants.ComponentName.FRONTEND.zip(),
root_dir=prerequisites.get_web_dir() / constants.Dirs.STATIC,
files_to_exclude=files_to_exclude,
exclude_venv_dirs=False,
)
@ -160,8 +161,8 @@ def zip_app(
if backend:
_zip(
component_name=constants.ComponentName.BACKEND,
target=os.path.join(zip_dest_dir, constants.ComponentName.BACKEND.zip()),
root_dir=".",
target=zip_dest_dir / constants.ComponentName.BACKEND.zip(),
root_dir=Path("."),
dirs_to_exclude={"__pycache__"},
files_to_exclude=files_to_exclude,
top_level_dirs_to_exclude={"assets"},
@ -236,6 +237,9 @@ def setup_frontend(
# Set the environment variables in client (env.json).
set_env_json()
# update the last reflex run time.
prerequisites.set_last_reflex_run_time()
# Disable the Next telemetry.
if disable_telemetry:
processes.new_process(
@ -266,5 +270,6 @@ def setup_frontend_prod(
build(deploy_url=get_config().deploy_url)
def _looks_like_venv_dir(dir_to_check: str) -> bool:
return os.path.exists(os.path.join(dir_to_check, "pyvenv.cfg"))
def _looks_like_venv_dir(dir_to_check: str | Path) -> bool:
dir_to_check = Path(dir_to_check)
return (dir_to_check / "pyvenv.cfg").exists()

View File

@ -19,10 +19,13 @@ async def windows_hot_reload_lifespan_hack():
import asyncio
import sys
while True:
sys.stderr.write("\0")
sys.stderr.flush()
await asyncio.sleep(0.5)
try:
while True:
sys.stderr.write("\0")
sys.stderr.flush()
await asyncio.sleep(0.5)
except asyncio.CancelledError:
pass
@contextlib.contextmanager
@ -84,6 +87,4 @@ def sqlmodel_field_has_primary_key(field) -> bool:
return True
if getattr(field.field_info, "sa_column", None) is None:
return False
if getattr(field.field_info.sa_column, "primary_key", None) is True:
return True
return False
return bool(getattr(field.field_info.sa_column, "primary_key", None))

View File

@ -123,3 +123,15 @@ class PrimitiveUnserializableToJSON(ReflexError, ValueError):
class InvalidLifespanTaskType(ReflexError, TypeError):
"""Raised when an invalid task type is registered as a lifespan task."""
class DynamicComponentMissingLibrary(ReflexError, ValueError):
"""Raised when a dynamic component is missing a library."""
class SetUndefinedStateVarError(ReflexError, AttributeError):
"""Raised when setting the value of a var without first declaring it."""
class StateSchemaMismatchError(ReflexError, TypeError):
"""Raised when the serialized schema of a state class does not match the current schema."""

View File

@ -284,7 +284,7 @@ def run_granian_backend(host, port, loglevel: LogLevel):
).serve()
except ImportError:
console.error(
'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian>=1.6.0"`)'
'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian[reload]>=1.6.0"`)'
)
os._exit(1)
@ -410,7 +410,7 @@ def run_granian_backend_prod(host, port, loglevel):
)
except ImportError:
console.error(
'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian>=1.6.0"`)'
'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian[reload]>=1.6.0"`)'
)

View File

@ -359,19 +359,7 @@ def format_prop(
# Handle event props.
if isinstance(prop, EventChain):
sig = inspect.signature(prop.args_spec) # type: ignore
if sig.parameters:
arg_def = ",".join(f"_{p}" for p in sig.parameters)
arg_def_expr = f"[{arg_def}]"
else:
# add a default argument for addEvents if none were specified in prop.args_spec
# used to trigger the preventDefault() on the event.
arg_def = "...args"
arg_def_expr = "args"
chain = ",".join([format_event(event) for event in prop.events])
event = f"addEvents([{chain}], {arg_def_expr}, {json_dumps(prop.event_actions)})"
prop = f"({arg_def}) => {event}"
return str(Var.create(prop))
# Handle other types.
elif isinstance(prop, str):

View File

@ -164,7 +164,7 @@ def use_system_bun() -> bool:
return use_system_install(constants.Bun.USE_SYSTEM_VAR)
def get_node_bin_path() -> str | None:
def get_node_bin_path() -> Path | None:
"""Get the node binary dir path.
Returns:
@ -173,8 +173,8 @@ def get_node_bin_path() -> str | None:
bin_path = Path(constants.Node.BIN_PATH)
if not bin_path.exists():
str_path = which("node")
return str(Path(str_path).parent.resolve()) if str_path else str_path
return str(bin_path.resolve())
return Path(str_path).parent.resolve() if str_path else None
return bin_path.resolve()
def get_node_path() -> str | None:
@ -196,7 +196,7 @@ def get_npm_path() -> str | None:
The path to the npm binary file.
"""
npm_path = Path(constants.Node.NPM_PATH)
if not npm_path.exists():
if use_system_node() or not npm_path.exists():
return str(which("npm"))
return str(npm_path)

View File

@ -2,9 +2,9 @@
from __future__ import annotations
import contextlib
import dataclasses
import functools
import glob
import importlib
import importlib.metadata
import json
@ -19,7 +19,6 @@ import tempfile
import time
import zipfile
from datetime import datetime
from fileinput import FileInput
from pathlib import Path
from types import ModuleType
from typing import Callable, List, Optional
@ -38,7 +37,7 @@ from reflex.config import Config, get_config
from reflex.utils import console, net, path_ops, processes
from reflex.utils.exceptions import GeneratedCodeHasNoFunctionDefs
from reflex.utils.format import format_library_name
from reflex.utils.registry import _get_best_registry
from reflex.utils.registry import _get_npm_registry
CURRENTLY_INSTALLING_NODE = False
@ -132,6 +131,14 @@ def get_or_set_last_reflex_version_check_datetime():
return last_version_check_datetime
def set_last_reflex_run_time():
"""Set the last Reflex run time."""
path_ops.update_json_file(
get_web_dir() / constants.Reflex.JSON,
{"last_reflex_run_datetime": str(datetime.now())},
)
def check_node_version() -> bool:
"""Check the version of Node.js.
@ -192,7 +199,7 @@ def get_bun_version() -> version.Version | None:
"""
try:
# Run the bun -v command and capture the output
result = processes.new_process([get_config().bun_path, "-v"], run=True)
result = processes.new_process([str(get_config().bun_path), "-v"], run=True)
return version.parse(result.stdout) # type: ignore
except FileNotFoundError:
return None
@ -217,7 +224,7 @@ def get_install_package_manager() -> str | None:
or windows_npm_escape_hatch()
):
return get_package_manager()
return get_config().bun_path
return str(get_config().bun_path)
def get_package_manager() -> str | None:
@ -394,9 +401,7 @@ def validate_app_name(app_name: str | None = None) -> str:
Raises:
Exit: if the app directory name is reflex or if the name is not standard for a python package name.
"""
app_name = (
app_name if app_name else os.getcwd().split(os.path.sep)[-1].replace("-", "_")
)
app_name = app_name if app_name else Path.cwd().name.replace("-", "_")
# Make sure the app is not named "reflex".
if app_name.lower() == constants.Reflex.MODULE_NAME:
console.error(
@ -430,7 +435,7 @@ def create_config(app_name: str):
def initialize_gitignore(
gitignore_file: str = constants.GitIgnore.FILE,
gitignore_file: Path = constants.GitIgnore.FILE,
files_to_ignore: set[str] = constants.GitIgnore.DEFAULTS,
):
"""Initialize the template .gitignore file.
@ -441,9 +446,10 @@ def initialize_gitignore(
"""
# Combine with the current ignored files.
current_ignore: set[str] = set()
if os.path.exists(gitignore_file):
with open(gitignore_file, "r") as f:
current_ignore |= set([line.strip() for line in f.readlines()])
if gitignore_file.exists():
current_ignore |= set(
line.strip() for line in gitignore_file.read_text().splitlines()
)
if files_to_ignore == current_ignore:
console.debug(f"{gitignore_file} already up to date.")
@ -451,9 +457,11 @@ def initialize_gitignore(
files_to_ignore |= current_ignore
# Write files to the .gitignore file.
with open(gitignore_file, "w", newline="\n") as f:
console.debug(f"Creating {gitignore_file}")
f.write(f"{(path_ops.join(sorted(files_to_ignore))).lstrip()}\n")
gitignore_file.touch(exist_ok=True)
console.debug(f"Creating {gitignore_file}")
gitignore_file.write_text(
"\n".join(sorted(files_to_ignore)) + "\n",
)
def initialize_requirements_txt():
@ -546,8 +554,8 @@ def initialize_app_directory(
# Rename the template app to the app name.
path_ops.mv(template_code_dir_name, app_name)
path_ops.mv(
os.path.join(app_name, template_name + constants.Ext.PY),
os.path.join(app_name, app_name + constants.Ext.PY),
Path(app_name) / (template_name + constants.Ext.PY),
Path(app_name) / (app_name + constants.Ext.PY),
)
# Fix up the imports.
@ -612,7 +620,7 @@ def initialize_package_json():
code = _compile_package_json()
output_path.write_text(code)
best_registry = _get_best_registry()
best_registry = _get_npm_registry()
bun_config_path = get_web_dir() / constants.Bun.CONFIG_PATH
bun_config_path.write_text(
f"""
@ -691,7 +699,7 @@ def _update_next_config(
def remove_existing_bun_installation():
"""Remove existing bun installation."""
console.debug("Removing existing bun installation.")
if os.path.exists(get_config().bun_path):
if Path(get_config().bun_path).exists():
path_ops.rm(constants.Bun.ROOT_PATH)
@ -731,7 +739,7 @@ def download_and_extract_fnm_zip():
# Download the zip file
url = constants.Fnm.INSTALL_URL
console.debug(f"Downloading {url}")
fnm_zip_file = os.path.join(constants.Fnm.DIR, f"{constants.Fnm.FILENAME}.zip")
fnm_zip_file = constants.Fnm.DIR / f"{constants.Fnm.FILENAME}.zip"
# Function to download and extract the FNM zip release.
try:
# Download the FNM zip release.
@ -770,7 +778,7 @@ def install_node():
return
path_ops.mkdir(constants.Fnm.DIR)
if not os.path.exists(constants.Fnm.EXE):
if not constants.Fnm.EXE.exists():
download_and_extract_fnm_zip()
if constants.IS_WINDOWS:
@ -827,7 +835,7 @@ def install_bun():
)
# Skip if bun is already installed.
if os.path.exists(get_config().bun_path) and get_bun_version() == version.parse(
if Path(get_config().bun_path).exists() and get_bun_version() == version.parse(
constants.Bun.VERSION
):
console.debug("Skipping bun installation as it is already installed.")
@ -842,7 +850,7 @@ def install_bun():
f"irm {constants.Bun.WINDOWS_INSTALL_URL}|iex",
],
env={
"BUN_INSTALL": constants.Bun.ROOT_PATH,
"BUN_INSTALL": str(constants.Bun.ROOT_PATH),
"BUN_VERSION": constants.Bun.VERSION,
},
shell=True,
@ -858,25 +866,26 @@ def install_bun():
download_and_run(
constants.Bun.INSTALL_URL,
f"bun-v{constants.Bun.VERSION}",
BUN_INSTALL=constants.Bun.ROOT_PATH,
BUN_INSTALL=str(constants.Bun.ROOT_PATH),
)
def _write_cached_procedure_file(payload: str, cache_file: str):
with open(cache_file, "w") as f:
f.write(payload)
def _write_cached_procedure_file(payload: str, cache_file: str | Path):
cache_file = Path(cache_file)
cache_file.write_text(payload)
def _read_cached_procedure_file(cache_file: str) -> str | None:
if os.path.exists(cache_file):
with open(cache_file, "r") as f:
return f.read()
def _read_cached_procedure_file(cache_file: str | Path) -> str | None:
cache_file = Path(cache_file)
if cache_file.exists():
return cache_file.read_text()
return None
def _clear_cached_procedure_file(cache_file: str):
if os.path.exists(cache_file):
os.remove(cache_file)
def _clear_cached_procedure_file(cache_file: str | Path):
cache_file = Path(cache_file)
if cache_file.exists():
cache_file.unlink()
def cached_procedure(cache_file: str, payload_fn: Callable[..., str]):
@ -977,7 +986,7 @@ def needs_reinit(frontend: bool = True) -> bool:
Raises:
Exit: If the app is not initialized.
"""
if not os.path.exists(constants.Config.FILE):
if not constants.Config.FILE.exists():
console.error(
f"[cyan]{constants.Config.FILE}[/cyan] not found. Move to the root folder of your project, or run [bold]{constants.Reflex.MODULE_NAME} init[/bold] to start a new project."
)
@ -988,7 +997,7 @@ def needs_reinit(frontend: bool = True) -> bool:
return False
# Make sure the .reflex directory exists.
if not os.path.exists(constants.Reflex.DIR):
if not constants.Reflex.DIR.exists():
return True
# Make sure the .web directory exists in frontend mode.
@ -1093,25 +1102,21 @@ def ensure_reflex_installation_id() -> Optional[int]:
"""
try:
initialize_reflex_user_directory()
installation_id_file = os.path.join(constants.Reflex.DIR, "installation_id")
installation_id_file = constants.Reflex.DIR / "installation_id"
installation_id = None
if os.path.exists(installation_id_file):
try:
with open(installation_id_file, "r") as f:
installation_id = int(f.read())
except Exception:
if installation_id_file.exists():
with contextlib.suppress(Exception):
installation_id = int(installation_id_file.read_text())
# If anything goes wrong at all... just regenerate.
# Like what? Examples:
# - file not exists
# - file not readable
# - content not parseable as an int
pass
if installation_id is None:
installation_id = random.getrandbits(128)
with open(installation_id_file, "w") as f:
f.write(str(installation_id))
installation_id_file.write_text(str(installation_id))
# If we get here, installation_id is definitely set
return installation_id
except Exception as e:
@ -1205,50 +1210,6 @@ def prompt_for_template(templates: list[Template]) -> str:
return templates[int(template)].name
def migrate_to_reflex():
"""Migration from Pynecone to Reflex."""
# Check if the old config file exists.
if not os.path.exists(constants.Config.PREVIOUS_FILE):
return
# Ask the user if they want to migrate.
action = console.ask(
"Pynecone project detected. Automatically upgrade to Reflex?",
choices=["y", "n"],
)
if action == "n":
return
# Rename pcconfig to rxconfig.
console.log(
f"[bold]Renaming {constants.Config.PREVIOUS_FILE} to {constants.Config.FILE}"
)
os.rename(constants.Config.PREVIOUS_FILE, constants.Config.FILE)
# Find all python files in the app directory.
file_pattern = os.path.join(get_config().app_name, "**/*.py")
file_list = glob.glob(file_pattern, recursive=True)
# Add the config file to the list of files to be migrated.
file_list.append(constants.Config.FILE)
# Migrate all files.
updates = {
"Pynecone": "Reflex",
"pynecone as pc": "reflex as rx",
"pynecone.io": "reflex.dev",
"pynecone": "reflex",
"pc.": "rx.",
"pcconfig": "rxconfig",
}
for file_path in file_list:
with FileInput(file_path, inplace=True) as file:
for line in file:
for old, new in updates.items():
line = line.replace(old, new)
print(line, end="")
def fetch_app_templates(version: str) -> dict[str, Template]:
"""Fetch a dict of templates from the templates repo using github API.
@ -1401,7 +1362,7 @@ def initialize_app(app_name: str, template: str | None = None):
from reflex.utils import telemetry
# Check if the app is already initialized.
if os.path.exists(constants.Config.FILE):
if constants.Config.FILE.exists():
telemetry.send("reinit")
return

View File

@ -156,7 +156,7 @@ def new_process(args, run: bool = False, show_logs: bool = False, **kwargs):
Raises:
Exit: When attempting to run a command with a None value.
"""
node_bin_path = path_ops.get_node_bin_path()
node_bin_path = str(path_ops.get_node_bin_path())
if not node_bin_path and not prerequisites.CURRENTLY_INSTALLING_NODE:
console.warn(
"The path to the Node binary could not be found. Please ensure that Node is properly "
@ -167,7 +167,7 @@ def new_process(args, run: bool = False, show_logs: bool = False, **kwargs):
console.error(f"Invalid command: {args}")
raise typer.Exit(1)
# Add the node bin path to the PATH environment variable.
env = {
env: dict[str, str] = {
**os.environ,
"PATH": os.pathsep.join(
[node_bin_path if node_bin_path else "", os.environ["PATH"]]

View File

@ -1,8 +1,10 @@
"""Utilities for working with registries."""
import os
import httpx
from reflex.utils import console
from reflex.utils import console, net
def latency(registry: str) -> int:
@ -15,7 +17,7 @@ def latency(registry: str) -> int:
int: The latency of the registry in microseconds.
"""
try:
return httpx.get(registry).elapsed.microseconds
return net.get(registry).elapsed.microseconds
except httpx.HTTPError:
console.info(f"Failed to connect to {registry}.")
return 10_000_000
@ -34,7 +36,7 @@ def average_latency(registry, attempts: int = 3) -> int:
return sum(latency(registry) for _ in range(attempts)) // attempts
def _get_best_registry() -> str:
def get_best_registry() -> str:
"""Get the best registry based on latency.
Returns:
@ -46,3 +48,15 @@ def _get_best_registry() -> str:
]
return min(registries, key=average_latency)
def _get_npm_registry() -> str:
"""Get npm registry. If environment variable is set, use it first.
Returns:
str:
"""
if npm_registry := os.environ.get("NPM_CONFIG_REGISTRY", ""):
return npm_registry
else:
return get_best_registry()

View File

@ -94,9 +94,7 @@ def _raise_on_missing_project_hash() -> bool:
False when compilation should be skipped (i.e. no .web directory is required).
Otherwise return True.
"""
if should_skip_compile():
return False
return True
return not should_skip_compile()
def _prepare_event(event: str, **kwargs) -> dict:

View File

@ -374,7 +374,7 @@ def get_base_class(cls: GenericType) -> Type:
if is_literal(cls):
# only literals of the same type are supported.
arg_type = type(get_args(cls)[0])
if not all(type(arg) == arg_type for arg in get_args(cls)):
if not all(type(arg) is arg_type for arg in get_args(cls)):
raise TypeError("only literals of the same type are supported")
return type(get_args(cls)[0])
@ -538,7 +538,7 @@ def is_backend_base_variable(name: str, cls: Type) -> bool:
if name in cls.__dict__:
value = cls.__dict__[name]
if type(value) == classmethod:
if type(value) is classmethod:
return False
if callable(value):
return False

View File

@ -1,8 +1,10 @@
"""Immutable-Based Var System."""
from .base import Field as Field
from .base import LiteralVar as LiteralVar
from .base import Var as Var
from .base import VarData as VarData
from .base import field as field
from .base import get_unique_variable_name as get_unique_variable_name
from .base import get_uuid_string_var as get_uuid_string_var
from .base import var_operation as var_operation

View File

@ -239,7 +239,7 @@ class Var(Generic[VAR_TYPE]):
**kwargs,
)
if (js_expr := kwargs.get("_js_expr", None)) is not None:
if (js_expr := kwargs.get("_js_expr")) is not None:
object.__setattr__(value_with_replaced, "_js_expr", js_expr)
return value_with_replaced
@ -385,6 +385,15 @@ class Var(Generic[VAR_TYPE]):
Returns:
The converted var.
"""
from reflex.event import (
EventChain,
EventChainVar,
EventSpec,
EventVar,
ToEventChainVarOperation,
ToEventVarOperation,
)
from .function import FunctionVar, ToFunctionOperation
from .number import (
BooleanVar,
@ -416,6 +425,10 @@ class Var(Generic[VAR_TYPE]):
return self.to(BooleanVar, output)
if fixed_output_type is None:
return ToNoneOperation.create(self)
if fixed_output_type is EventSpec:
return self.to(EventVar, output)
if fixed_output_type is EventChain:
return self.to(EventChainVar, output)
if issubclass(fixed_output_type, Base):
return self.to(ObjectVar, output)
if dataclasses.is_dataclass(fixed_output_type) and not issubclass(
@ -453,10 +466,13 @@ class Var(Generic[VAR_TYPE]):
if issubclass(output, StringVar):
return ToStringOperation.create(self, var_type or str)
if issubclass(output, (ObjectVar, Base)):
return ToObjectOperation.create(self, var_type or dict)
if issubclass(output, EventVar):
return ToEventVarOperation.create(self, var_type or EventSpec)
if dataclasses.is_dataclass(output):
if issubclass(output, EventChainVar):
return ToEventChainVarOperation.create(self, var_type or EventChain)
if issubclass(output, (ObjectVar, Base)):
return ToObjectOperation.create(self, var_type or dict)
if issubclass(output, FunctionVar):
@ -469,6 +485,9 @@ class Var(Generic[VAR_TYPE]):
if issubclass(output, NoneVar):
return ToNoneOperation.create(self)
if dataclasses.is_dataclass(output):
return ToObjectOperation.create(self, var_type or dict)
# If we can't determine the first argument, we just replace the _var_type.
if not issubclass(output, Var) or var_type is None:
return dataclasses.replace(
@ -494,6 +513,8 @@ class Var(Generic[VAR_TYPE]):
Raises:
TypeError: If the type is not supported for guessing.
"""
from reflex.event import EventChain, EventChainVar, EventSpec, EventVar
from .number import BooleanVar, NumberVar
from .object import ObjectVar
from .sequence import ArrayVar, StringVar
@ -526,6 +547,10 @@ class Var(Generic[VAR_TYPE]):
return self
if fixed_type is Literal:
args = get_args(var_type)
fixed_type = unionize(*(type(arg) for arg in args))
if not inspect.isclass(fixed_type):
raise TypeError(f"Unsupported type {var_type} for guess_type.")
@ -539,6 +564,10 @@ class Var(Generic[VAR_TYPE]):
return self.to(ArrayVar, self._var_type)
if issubclass(fixed_type, str):
return self.to(StringVar, self._var_type)
if issubclass(fixed_type, EventSpec):
return self.to(EventVar, self._var_type)
if issubclass(fixed_type, EventChain):
return self.to(EventChainVar, self._var_type)
if issubclass(fixed_type, Base):
return self.to(ObjectVar, self._var_type)
if dataclasses.is_dataclass(fixed_type):
@ -1029,47 +1058,22 @@ class LiteralVar(Var):
if value is None:
return LiteralNoneVar.create(_var_data=_var_data)
from reflex.event import EventChain, EventHandler, EventSpec
from reflex.event import (
EventChain,
EventHandler,
EventSpec,
LiteralEventChainVar,
LiteralEventVar,
)
from reflex.utils.format import get_event_handler_parts
from .function import ArgsFunctionOperation, FunctionStringVar
from .object import LiteralObjectVar
if isinstance(value, EventSpec):
event_name = LiteralVar.create(
".".join(filter(None, get_event_handler_parts(value.handler)))
)
event_args = LiteralVar.create(
{str(name): value for name, value in value.args}
)
event_client_name = LiteralVar.create(value.client_handler_name)
return FunctionStringVar("Event").call(
event_name,
event_args,
*([event_client_name] if value.client_handler_name else []),
)
return LiteralEventVar.create(value, _var_data=_var_data)
if isinstance(value, EventChain):
sig = inspect.signature(value.args_spec) # type: ignore
if sig.parameters:
arg_def = tuple((f"_{p}" for p in sig.parameters))
arg_def_expr = LiteralVar.create([Var(_js_expr=arg) for arg in arg_def])
else:
# add a default argument for addEvents if none were specified in value.args_spec
# used to trigger the preventDefault() on the event.
arg_def = ("...args",)
arg_def_expr = Var(_js_expr="args")
return ArgsFunctionOperation.create(
arg_def,
FunctionStringVar.create("addEvents").call(
LiteralVar.create(
[LiteralVar.create(event) for event in value.events]
),
arg_def_expr,
LiteralVar.create(value.event_actions),
),
)
return LiteralEventChainVar.create(value, _var_data=_var_data)
if isinstance(value, EventHandler):
return Var(_js_expr=".".join(filter(None, get_event_handler_parts(value))))
@ -2126,9 +2130,16 @@ class NoneVar(Var[None]):
"""A var representing None."""
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class LiteralNoneVar(LiteralVar, NoneVar):
"""A var representing None."""
_var_value: None = None
def json(self) -> str:
"""Serialize the var to a JSON string.
@ -2821,3 +2832,68 @@ def dispatch(
_var_data=var_data,
_var_type=result_var_type,
).guess_type()
V = TypeVar("V")
class Field(Generic[T]):
"""Shadow class for Var to allow for type hinting in the IDE."""
def __set__(self, instance, value: T):
"""Set the Var.
Args:
instance: The instance of the class setting the Var.
value: The value to set the Var to.
"""
@overload
def __get__(self: Field[bool], instance: None, owner) -> BooleanVar: ...
@overload
def __get__(self: Field[int], instance: None, owner) -> NumberVar: ...
@overload
def __get__(self: Field[str], instance: None, owner) -> StringVar: ...
@overload
def __get__(self: Field[None], instance: None, owner) -> NoneVar: ...
@overload
def __get__(
self: Field[List[V]] | Field[Set[V]] | Field[Tuple[V, ...]],
instance: None,
owner,
) -> ArrayVar[List[V]]: ...
@overload
def __get__(
self: Field[Dict[str, V]], instance: None, owner
) -> ObjectVar[Dict[str, V]]: ...
@overload
def __get__(self, instance: None, owner) -> Var[T]: ...
@overload
def __get__(self, instance, owner) -> T: ...
def __get__(self, instance, owner): # type: ignore
"""Get the Var.
Args:
instance: The instance of the class accessing the Var.
owner: The class that the Var is attached to.
"""
def field(value: T) -> Field[T]:
"""Create a Field with a value.
Args:
value: The value of the Field.
Returns:
The Field.
"""
return value # type: ignore

View File

@ -119,6 +119,8 @@ class ObjectVar(Var[OBJECT_TYPE]):
"""
return object_entries_operation(self)
items = entries
def merge(self, other: ObjectVar):
"""Merge two objects.

View File

@ -884,6 +884,12 @@ class ArrayVar(Var[ARRAY_VAR_TYPE]):
i: int | NumberVar,
) -> ArrayVar[Set[INNER_ARRAY_VAR]]: ...
@overload
def __getitem__(
self: ARRAY_VAR_OF_LIST_ELEMENT[Tuple[KEY_TYPE, VALUE_TYPE]],
i: int | NumberVar,
) -> ArrayVar[Tuple[KEY_TYPE, VALUE_TYPE]]: ...
@overload
def __getitem__(
self: ARRAY_VAR_OF_LIST_ELEMENT[Tuple[INNER_ARRAY_VAR, ...]],

View File

@ -46,6 +46,7 @@ def CallScript():
inline_counter: int = 0
external_counter: int = 0
value: str = "Initial"
last_result: str = ""
def call_script_callback(self, result):
self.results.append(result)
@ -137,6 +138,32 @@ def CallScript():
callback=CallScriptState.set_external_counter, # type: ignore
)
def call_with_var_f_string(self):
return rx.call_script(
f"{rx.Var('inline_counter')} + {rx.Var('external_counter')}",
callback=CallScriptState.set_last_result, # type: ignore
)
def call_with_var_str_cast(self):
return rx.call_script(
f"{str(rx.Var('inline_counter'))} + {str(rx.Var('external_counter'))}",
callback=CallScriptState.set_last_result, # type: ignore
)
def call_with_var_f_string_wrapped(self):
return rx.call_script(
rx.Var(f"{rx.Var('inline_counter')} + {rx.Var('external_counter')}"),
callback=CallScriptState.set_last_result, # type: ignore
)
def call_with_var_str_cast_wrapped(self):
return rx.call_script(
rx.Var(
f"{str(rx.Var('inline_counter'))} + {str(rx.Var('external_counter'))}"
),
callback=CallScriptState.set_last_result, # type: ignore
)
def reset_(self):
yield rx.call_script("inline_counter = 0; external_counter = 0")
self.reset()
@ -234,6 +261,68 @@ def CallScript():
id="update_value",
),
rx.button("Reset", id="reset", on_click=CallScriptState.reset_),
rx.input(
value=CallScriptState.last_result,
id="last_result",
read_only=True,
on_click=CallScriptState.set_last_result(""), # type: ignore
),
rx.button(
"call_with_var_f_string",
on_click=CallScriptState.call_with_var_f_string,
id="call_with_var_f_string",
),
rx.button(
"call_with_var_str_cast",
on_click=CallScriptState.call_with_var_str_cast,
id="call_with_var_str_cast",
),
rx.button(
"call_with_var_f_string_wrapped",
on_click=CallScriptState.call_with_var_f_string_wrapped,
id="call_with_var_f_string_wrapped",
),
rx.button(
"call_with_var_str_cast_wrapped",
on_click=CallScriptState.call_with_var_str_cast_wrapped,
id="call_with_var_str_cast_wrapped",
),
rx.button(
"call_with_var_f_string_inline",
on_click=rx.call_script(
f"{rx.Var('inline_counter')} + {CallScriptState.last_result}",
callback=CallScriptState.set_last_result, # type: ignore
),
id="call_with_var_f_string_inline",
),
rx.button(
"call_with_var_str_cast_inline",
on_click=rx.call_script(
f"{str(rx.Var('inline_counter'))} + {str(rx.Var('external_counter'))}",
callback=CallScriptState.set_last_result, # type: ignore
),
id="call_with_var_str_cast_inline",
),
rx.button(
"call_with_var_f_string_wrapped_inline",
on_click=rx.call_script(
rx.Var(
f"{rx.Var('inline_counter')} + {CallScriptState.last_result}"
),
callback=CallScriptState.set_last_result, # type: ignore
),
id="call_with_var_f_string_wrapped_inline",
),
rx.button(
"call_with_var_str_cast_wrapped_inline",
on_click=rx.call_script(
rx.Var(
f"{str(rx.Var('inline_counter'))} + {str(rx.Var('external_counter'))}"
),
callback=CallScriptState.set_last_result, # type: ignore
),
id="call_with_var_str_cast_wrapped_inline",
),
)
@ -363,3 +452,73 @@ def test_call_script(
call_script.poll_for_content(update_value_button, exp_not_equal="Initial")
== "updated"
)
def test_call_script_w_var(
call_script: AppHarness,
driver: WebDriver,
):
"""Test evaluating javascript expressions containing Vars.
Args:
call_script: harness for CallScript app.
driver: WebDriver instance.
"""
assert_token(driver)
last_result = driver.find_element(By.ID, "last_result")
assert last_result.get_attribute("value") == ""
inline_return_button = driver.find_element(By.ID, "inline_return")
call_with_var_f_string_button = driver.find_element(By.ID, "call_with_var_f_string")
call_with_var_str_cast_button = driver.find_element(By.ID, "call_with_var_str_cast")
call_with_var_f_string_wrapped_button = driver.find_element(
By.ID, "call_with_var_f_string_wrapped"
)
call_with_var_str_cast_wrapped_button = driver.find_element(
By.ID, "call_with_var_str_cast_wrapped"
)
call_with_var_f_string_inline_button = driver.find_element(
By.ID, "call_with_var_f_string_inline"
)
call_with_var_str_cast_inline_button = driver.find_element(
By.ID, "call_with_var_str_cast_inline"
)
call_with_var_f_string_wrapped_inline_button = driver.find_element(
By.ID, "call_with_var_f_string_wrapped_inline"
)
call_with_var_str_cast_wrapped_inline_button = driver.find_element(
By.ID, "call_with_var_str_cast_wrapped_inline"
)
inline_return_button.click()
call_with_var_f_string_button.click()
assert call_script.poll_for_value(last_result, exp_not_equal="") == "1"
inline_return_button.click()
call_with_var_str_cast_button.click()
assert call_script.poll_for_value(last_result, exp_not_equal="1") == "2"
inline_return_button.click()
call_with_var_f_string_wrapped_button.click()
assert call_script.poll_for_value(last_result, exp_not_equal="2") == "3"
inline_return_button.click()
call_with_var_str_cast_wrapped_button.click()
assert call_script.poll_for_value(last_result, exp_not_equal="3") == "4"
inline_return_button.click()
call_with_var_f_string_inline_button.click()
assert call_script.poll_for_value(last_result, exp_not_equal="4") == "9"
inline_return_button.click()
call_with_var_str_cast_inline_button.click()
assert call_script.poll_for_value(last_result, exp_not_equal="9") == "6"
inline_return_button.click()
call_with_var_f_string_wrapped_inline_button.click()
assert call_script.poll_for_value(last_result, exp_not_equal="6") == "13"
inline_return_button.click()
call_with_var_str_cast_wrapped_inline_button.click()
assert call_script.poll_for_value(last_result, exp_not_equal="13") == "8"

View File

@ -5,6 +5,7 @@ from typing import Generator
import pytest
from selenium.webdriver.common.by import By
from reflex.state import State, _substate_key
from reflex.testing import AppHarness
from . import utils
@ -12,13 +13,21 @@ from . import utils
def ComponentStateApp():
"""App using per component state."""
from typing import Generic, TypeVar
import reflex as rx
class MultiCounter(rx.ComponentState):
E = TypeVar("E")
class MultiCounter(rx.ComponentState, Generic[E]):
count: int = 0
_be: E
_be_int: int
_be_str: str = "42"
def increment(self):
self.count += 1
self._be = self.count # type: ignore
@classmethod
def get_component(cls, *children, **props):
@ -48,6 +57,14 @@ def ComponentStateApp():
on_click=mc_a.State.increment, # type: ignore
id="inc-a",
),
rx.text(
mc_a.State.get_name() if mc_a.State is not None else "",
id="a_state_name",
),
rx.text(
mc_b.State.get_name() if mc_b.State is not None else "",
id="b_state_name",
),
)
@ -80,6 +97,7 @@ async def test_component_state_app(component_state_app: AppHarness):
ss = utils.SessionStorage(driver)
assert AppHarness._poll_for(lambda: ss.get("token") is not None), "token not found"
root_state_token = _substate_key(ss.get("token"), State)
count_a = driver.find_element(By.ID, "count-a")
count_b = driver.find_element(By.ID, "count-b")
@ -87,6 +105,18 @@ async def test_component_state_app(component_state_app: AppHarness):
button_b = driver.find_element(By.ID, "button-b")
button_inc_a = driver.find_element(By.ID, "inc-a")
# Check that backend vars in mixins are okay
a_state_name = driver.find_element(By.ID, "a_state_name").text
b_state_name = driver.find_element(By.ID, "b_state_name").text
root_state = await component_state_app.get_state(root_state_token)
a_state = root_state.substates[a_state_name]
b_state = root_state.substates[b_state_name]
assert a_state._backend_vars == a_state.backend_vars
assert a_state._backend_vars == b_state._backend_vars
assert a_state._backend_vars["_be"] is None
assert a_state._backend_vars["_be_int"] == 0
assert a_state._backend_vars["_be_str"] == "42"
assert count_a.text == "0"
button_a.click()
@ -98,6 +128,14 @@ async def test_component_state_app(component_state_app: AppHarness):
button_inc_a.click()
assert component_state_app.poll_for_content(count_a, exp_not_equal="2") == "3"
root_state = await component_state_app.get_state(root_state_token)
a_state = root_state.substates[a_state_name]
b_state = root_state.substates[b_state_name]
assert a_state._backend_vars != a_state.backend_vars
assert a_state._be == a_state._backend_vars["_be"] == 3
assert b_state._be is None
assert b_state._backend_vars["_be"] is None
assert count_b.text == "0"
button_b.click()
@ -105,3 +143,9 @@ async def test_component_state_app(component_state_app: AppHarness):
button_b.click()
assert component_state_app.poll_for_content(count_b, exp_not_equal="1") == "2"
root_state = await component_state_app.get_state(root_state_token)
a_state = root_state.substates[a_state_name]
b_state = root_state.substates[b_state_name]
assert b_state._backend_vars != b_state.backend_vars
assert b_state._be == b_state._backend_vars["_be"] == 2

View File

@ -65,7 +65,9 @@ def DynamicComponents():
DynamicComponentsState.client_token_component,
DynamicComponentsState.button,
rx.text(
DynamicComponentsState._evaluate(lambda state: factorial(state.value)),
DynamicComponentsState._evaluate(
lambda state: factorial(state.value), of_type=int
),
id="factorial",
),
)

View File

@ -23,11 +23,15 @@ def DynamicRoute():
order: List[str] = []
def on_load(self):
self.order.append(f"{self.router.page.path}-{self.page_id or 'no page id'}")
page_data = f"{self.router.page.path}-{self.page_id or 'no page id'}"
print(f"on_load: {page_data}")
self.order.append(page_data)
def on_load_redir(self):
query_params = self.router.page.params
self.order.append(f"on_load_redir-{query_params}")
page_data = f"on_load_redir-{query_params}"
print(f"on_load_redir: {page_data}")
self.order.append(page_data)
return rx.redirect(f"/page/{query_params['page_id']}")
@rx.var
@ -41,13 +45,13 @@ def DynamicRoute():
return rx.fragment(
rx.input(
value=DynamicState.router.session.client_token,
is_read_only=True,
read_only=True,
id="token",
),
rx.input(value=rx.State.page_id, is_read_only=True, id="page_id"), # type: ignore
rx.input(value=rx.State.page_id, read_only=True, id="page_id"), # type: ignore
rx.input(
value=DynamicState.router.page.raw_path,
is_read_only=True,
read_only=True,
id="raw_path",
),
rx.link("index", href="/", id="link_index"),
@ -221,8 +225,11 @@ def poll_for_order(
dynamic_state_name
].order == exp_order
await AppHarness._poll_for_async(_check)
assert (await _backend_state()).substates[dynamic_state_name].order == exp_order
await AppHarness._poll_for_async(_check, timeout=60)
assert (
list((await _backend_state()).substates[dynamic_state_name].order)
== exp_order
)
return _poll_for_order

View File

@ -8,7 +8,7 @@ import pytest
import requests
def check_urls(repo_dir):
def check_urls(repo_dir: Path):
"""Check that all URLs in the repo are valid and secure.
Args:
@ -21,33 +21,33 @@ def check_urls(repo_dir):
errors = []
for root, _dirs, files in os.walk(repo_dir):
if "__pycache__" in root:
root = Path(root)
if root.stem == "__pycache__":
continue
for file_name in files:
if not file_name.endswith(".py") and not file_name.endswith(".md"):
continue
file_path = os.path.join(root, file_name)
file_path = root / file_name
try:
with open(file_path, "r", encoding="utf-8", errors="ignore") as file:
for line in file:
urls = url_pattern.findall(line)
for url in set(urls):
if url.startswith("http://"):
errors.append(
f"Found insecure HTTP URL: {url} in {file_path}"
)
url = url.strip('"\n')
try:
response = requests.head(
url, allow_redirects=True, timeout=5
)
response.raise_for_status()
except requests.RequestException as e:
errors.append(
f"Error accessing URL: {url} in {file_path} | Error: {e}, , Check your path ends with a /"
)
for line in file_path.read_text().splitlines():
urls = url_pattern.findall(line)
for url in set(urls):
if url.startswith("http://"):
errors.append(
f"Found insecure HTTP URL: {url} in {file_path}"
)
url = url.strip('"\n')
try:
response = requests.head(
url, allow_redirects=True, timeout=5
)
response.raise_for_status()
except requests.RequestException as e:
errors.append(
f"Error accessing URL: {url} in {file_path} | Error: {e}, , Check your path ends with a /"
)
except Exception as e:
errors.append(f"Error reading file: {file_path} | Error: {e}")
@ -58,7 +58,7 @@ def check_urls(repo_dir):
"repo_dir",
[Path(__file__).resolve().parent.parent / "reflex"],
)
def test_find_and_check_urls(repo_dir):
def test_find_and_check_urls(repo_dir: Path):
"""Test that all URLs in the repo are valid and secure.
Args:

View File

@ -1,4 +1,4 @@
import os
from pathlib import Path
from typing import List
import pytest
@ -130,7 +130,7 @@ def test_compile_stylesheets(tmp_path, mocker):
]
assert compiler.compile_root_stylesheet(stylesheets) == (
os.path.join(".web", "styles", "styles.css"),
str(Path(".web") / "styles" / "styles.css"),
f"@import url('./tailwind.css'); \n"
f"@import url('https://fonts.googleapis.com/css?family=Sofia&effect=neon|outline|emboss|shadow-multiple'); \n"
f"@import url('https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css'); \n"
@ -164,7 +164,7 @@ def test_compile_stylesheets_exclude_tailwind(tmp_path, mocker):
]
assert compiler.compile_root_stylesheet(stylesheets) == (
os.path.join(".web", "styles", "styles.css"),
str(Path(".web") / "styles" / "styles.css"),
"@import url('../public/styles.css'); \n",
)

View File

@ -58,14 +58,14 @@ def test_script_event_handler():
)
render_dict = component.render()
assert (
f'onReady={{((...args) => ((addEvents([(Event("{EvState.get_full_name()}.on_ready", ({{ }})))], args, ({{ }})))))}}'
f'onReady={{((...args) => ((addEvents([(Event("{EvState.get_full_name()}.on_ready", ({{ }}), ({{ }})))], args, ({{ }})))))}}'
in render_dict["props"]
)
assert (
f'onLoad={{((...args) => ((addEvents([(Event("{EvState.get_full_name()}.on_load", ({{ }})))], args, ({{ }})))))}}'
f'onLoad={{((...args) => ((addEvents([(Event("{EvState.get_full_name()}.on_load", ({{ }}), ({{ }})))], args, ({{ }})))))}}'
in render_dict["props"]
)
assert (
f'onError={{((...args) => ((addEvents([(Event("{EvState.get_full_name()}.on_error", ({{ }})))], args, ({{ }})))))}}'
f'onError={{((...args) => ((addEvents([(Event("{EvState.get_full_name()}.on_error", ({{ }}), ({{ }})))], args, ({{ }})))))}}'
in render_dict["props"]
)

View File

@ -6,7 +6,7 @@ import pytest
from reflex.components.base.fragment import Fragment
from reflex.components.core.cond import Cond, cond
from reflex.components.radix.themes.typography.text import Text
from reflex.state import BaseState, State
from reflex.state import BaseState
from reflex.utils.format import format_state_name
from reflex.vars.base import LiteralVar, Var, computed_var
@ -45,7 +45,7 @@ def test_validate_cond(cond_state: BaseState):
Text.create("cond is True"),
Text.create("cond is False"),
)
cond_dict = cond_component.render() if type(cond_component) == Fragment else {}
cond_dict = cond_component.render() if type(cond_component) is Fragment else {}
assert cond_dict["name"] == "Fragment"
[condition] = cond_dict["children"]
@ -124,7 +124,7 @@ def test_cond_no_else():
def test_cond_computed_var():
"""Test if cond works with computed vars."""
class CondStateComputed(State):
class CondStateComputed(BaseState):
@computed_var
def computed_int(self) -> int:
return 0

View File

@ -47,7 +47,7 @@ class ForEachState(BaseState):
color_index_tuple: Tuple[int, str] = (0, "red")
class TestComponentState(ComponentState):
class ComponentStateTest(ComponentState):
"""A test component state."""
foo: bool
@ -67,7 +67,7 @@ class TestComponentState(ComponentState):
def display_color(color):
assert color._var_type == str
assert color._var_type is str
return box(text(color))
@ -106,18 +106,18 @@ def display_nested_color_with_shades_v2(color):
def display_color_tuple(color):
assert color._var_type == str
assert color._var_type is str
return box(text(color))
def display_colors_set(color):
assert color._var_type == str
assert color._var_type is str
return box(text(color))
def display_nested_list_element(element: ArrayVar[List[str]], index: NumberVar[int]):
assert element._var_type == List[str]
assert index._var_type == int
assert index._var_type is int
return box(text(element[index]))
@ -240,7 +240,7 @@ def test_foreach_render(state_var, render_fn, render_dict):
arg_index = rend["arg_index"]
assert isinstance(arg_index, Var)
assert arg_index._js_expr not in seen_index_vars
assert arg_index._var_type == int
assert arg_index._var_type is int
seen_index_vars.add(arg_index._js_expr)
@ -288,5 +288,5 @@ def test_foreach_component_state():
with pytest.raises(TypeError):
Foreach.create(
ForEachState.colors_list,
TestComponentState.create,
ComponentStateTest.create,
)

View File

@ -41,15 +41,15 @@ def test_match_components():
assert len(match_cases) == 6
assert match_cases[0][0]._js_expr == "1"
assert match_cases[0][0]._var_type == int
assert match_cases[0][0]._var_type is int
first_return_value_render = match_cases[0][1].render()
assert first_return_value_render["name"] == "RadixThemesText"
assert first_return_value_render["children"][0]["contents"] == '{"first value"}'
assert match_cases[1][0]._js_expr == "2"
assert match_cases[1][0]._var_type == int
assert match_cases[1][0]._var_type is int
assert match_cases[1][1]._js_expr == "3"
assert match_cases[1][1]._var_type == int
assert match_cases[1][1]._var_type is int
second_return_value_render = match_cases[1][2].render()
assert second_return_value_render["name"] == "RadixThemesText"
assert second_return_value_render["children"][0]["contents"] == '{"second value"}'
@ -61,7 +61,7 @@ def test_match_components():
assert third_return_value_render["children"][0]["contents"] == '{"third value"}'
assert match_cases[3][0]._js_expr == '"random"'
assert match_cases[3][0]._var_type == str
assert match_cases[3][0]._var_type is str
fourth_return_value_render = match_cases[3][1].render()
assert fourth_return_value_render["name"] == "RadixThemesText"
assert fourth_return_value_render["children"][0]["contents"] == '{"fourth value"}'
@ -73,7 +73,7 @@ def test_match_components():
assert fifth_return_value_render["children"][0]["contents"] == '{"fifth value"}'
assert match_cases[5][0]._js_expr == f"({MatchState.get_name()}.num + 1)"
assert match_cases[5][0]._var_type == int
assert match_cases[5][0]._var_type is int
fifth_return_value_render = match_cases[5][1].render()
assert fifth_return_value_render["name"] == "RadixThemesText"
assert fifth_return_value_render["children"][0]["contents"] == '{"sixth value"}'

View File

@ -11,7 +11,7 @@ from reflex.state import State
from reflex.vars.base import LiteralVar, Var
class TestUploadState(State):
class UploadStateTest(State):
"""Test upload state."""
def drop_handler(self, files):
@ -55,7 +55,7 @@ def test_upload_create():
up_comp_2 = Upload.create(
id="foo_id",
on_drop=TestUploadState.drop_handler([]), # type: ignore
on_drop=UploadStateTest.drop_handler([]), # type: ignore
)
assert isinstance(up_comp_2, Upload)
assert up_comp_2.is_used
@ -65,7 +65,7 @@ def test_upload_create():
up_comp_3 = Upload.create(
id="foo_id",
on_drop=TestUploadState.drop_handler,
on_drop=UploadStateTest.drop_handler,
)
assert isinstance(up_comp_3, Upload)
assert up_comp_3.is_used
@ -75,7 +75,7 @@ def test_upload_create():
up_comp_4 = Upload.create(
id="foo_id",
on_drop=TestUploadState.not_drop_handler([]), # type: ignore
on_drop=UploadStateTest.not_drop_handler([]), # type: ignore
)
assert isinstance(up_comp_4, Upload)
assert up_comp_4.is_used
@ -91,7 +91,7 @@ def test_styled_upload_create():
styled_up_comp_2 = StyledUpload.create(
id="foo_id",
on_drop=TestUploadState.drop_handler([]), # type: ignore
on_drop=UploadStateTest.drop_handler([]), # type: ignore
)
assert isinstance(styled_up_comp_2, StyledUpload)
assert styled_up_comp_2.is_used
@ -101,7 +101,7 @@ def test_styled_upload_create():
styled_up_comp_3 = StyledUpload.create(
id="foo_id",
on_drop=TestUploadState.drop_handler,
on_drop=UploadStateTest.drop_handler,
)
assert isinstance(styled_up_comp_3, StyledUpload)
assert styled_up_comp_3.is_used
@ -111,7 +111,7 @@ def test_styled_upload_create():
styled_up_comp_4 = StyledUpload.create(
id="foo_id",
on_drop=TestUploadState.not_drop_handler([]), # type: ignore
on_drop=UploadStateTest.not_drop_handler([]), # type: ignore
)
assert isinstance(styled_up_comp_4, StyledUpload)
assert styled_up_comp_4.is_used

View File

@ -832,7 +832,7 @@ def test_component_event_trigger_arbitrary_args():
assert comp.render()["props"][0] == (
"onFoo={((__e, _alpha, _bravo, _charlie) => ((addEvents("
f'[(Event("{C1State.get_full_name()}.mock_handler", ({{ ["_e"] : __e["target"]["value"], ["_bravo"] : _bravo["nested"], ["_charlie"] : (_charlie["custom"] + 42) }})))], '
f'[(Event("{C1State.get_full_name()}.mock_handler", ({{ ["_e"] : __e["target"]["value"], ["_bravo"] : _bravo["nested"], ["_charlie"] : (_charlie["custom"] + 42) }}), ({{ }})))], '
"[__e, _alpha, _bravo, _charlie], ({ })))))}"
)
@ -1178,7 +1178,7 @@ TEST_VAR = LiteralVar.create("test")._replace(
)
FORMATTED_TEST_VAR = LiteralVar.create(f"foo{TEST_VAR}bar")
STYLE_VAR = TEST_VAR._replace(_js_expr="style")
EVENT_CHAIN_VAR = TEST_VAR._replace(_var_type=EventChain)
EVENT_CHAIN_VAR = TEST_VAR.to(EventChain)
ARG_VAR = Var(_js_expr="arg")
TEST_VAR_DICT_OF_DICT = LiteralVar.create({"a": {"b": "test"}})._replace(
@ -2159,7 +2159,7 @@ class TriggerState(rx.State):
rx.text("random text", on_click=TriggerState.do_something),
rx.text(
"random text",
on_click=Var(_js_expr="toggleColorMode", _var_type=EventChain),
on_click=Var(_js_expr="toggleColorMode").to(EventChain),
),
),
True,
@ -2169,7 +2169,7 @@ class TriggerState(rx.State):
rx.text("random text", on_click=rx.console_log("log")),
rx.text(
"random text",
on_click=Var(_js_expr="toggleColorMode", _var_type=EventChain),
on_click=Var(_js_expr="toggleColorMode").to(EventChain),
),
),
False,

View File

@ -119,7 +119,7 @@ def test_format_cond_tag():
tag_dict["false_value"],
)
assert cond._js_expr == "logged_in"
assert cond._var_type == bool
assert cond._var_type is bool
assert true_value["name"] == "h1"
assert true_value["contents"] == "True content"

View File

@ -1,5 +1,6 @@
"""Test fixtures."""
import asyncio
import contextlib
import os
import platform
@ -24,6 +25,11 @@ from .states import (
)
def pytest_configure(config):
if config.getoption("asyncio_mode") == "auto":
asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy())
@pytest.fixture
def app() -> App:
"""A base app.

View File

@ -765,7 +765,8 @@ async def test_upload_file(tmp_path, state, delta, token: str, mocker):
)
state._tmp_path = tmp_path
# The App state must be the "root" of the state tree
app = App(state=State)
app = App()
app._enable_state()
app.event_namespace.emit = AsyncMock() # type: ignore
current_state = await app.state_manager.get_state(_substate_key(token, state))
data = b"This is binary data"

View File

@ -192,4 +192,4 @@ def test_reflex_dir_env_var(monkeypatch, tmp_path):
mp_ctx = multiprocessing.get_context(method="spawn")
with mp_ctx.Pool(processes=1) as pool:
assert pool.apply(reflex_dir_constant) == str(tmp_path)
assert pool.apply(reflex_dir_constant) == tmp_path

View File

@ -9,10 +9,11 @@ import json
import os
import sys
from textwrap import dedent
from typing import Any, Callable, Dict, Generator, List, Optional, Union
from typing import Any, AsyncGenerator, Callable, Dict, List, Optional, Union
from unittest.mock import AsyncMock, Mock
import pytest
import pytest_asyncio
from plotly.graph_objects import Figure
import reflex as rx
@ -41,6 +42,7 @@ from reflex.state import (
)
from reflex.testing import chdir
from reflex.utils import format, prerequisites, types
from reflex.utils.exceptions import SetUndefinedStateVarError
from reflex.utils.format import json_dumps
from reflex.vars.base import ComputedVar, Var
from tests.units.states.mutation import MutableSQLAModel, MutableTestState
@ -103,6 +105,7 @@ class TestState(BaseState):
complex: Dict[int, Object] = {1: Object(), 2: Object()}
fig: Figure = Figure()
dt: datetime.datetime = datetime.datetime.fromisoformat("1989-11-09T18:53:00+01:00")
_backend: int = 0
@ComputedVar
def sum(self) -> float:
@ -272,9 +275,9 @@ def test_base_class_vars(test_state):
assert isinstance(prop, Var)
assert prop._js_expr.split(".")[-1] == field
assert cls.num1._var_type == int
assert cls.num2._var_type == float
assert cls.key._var_type == str
assert cls.num1._var_type is int
assert cls.num2._var_type is float
assert cls.key._var_type is str
def test_computed_class_var(test_state):
@ -524,7 +527,7 @@ def test_set_class_var():
TestState._set_var(Var(_js_expr="num3", _var_type=int)._var_set_state(TestState))
var = TestState.num3 # type: ignore
assert var._js_expr == TestState.get_full_name() + ".num3"
assert var._var_type == int
assert var._var_type is int
assert var._var_state == TestState.get_full_name()
@ -704,6 +707,7 @@ def test_reset(test_state, child_state):
# Set some values.
test_state.num1 = 1
test_state.num2 = 2
test_state._backend = 3
child_state.value = "test"
# Reset the state.
@ -712,6 +716,7 @@ def test_reset(test_state, child_state):
# The values should be reset.
assert test_state.num1 == 0
assert test_state.num2 == 3.14
assert test_state._backend == 0
assert child_state.value == ""
expected_dirty_vars = {
@ -727,6 +732,7 @@ def test_reset(test_state, child_state):
"map_key",
"mapping",
"dt",
"_backend",
}
# The dirty vars should be reset.
@ -1596,8 +1602,10 @@ async def test_state_with_invalid_yield(capsys, mock_app):
assert "must only return/yield: None, Events or other EventHandlers" in captured.out
@pytest.fixture(scope="function", params=["in_process", "disk", "redis"])
def state_manager(request) -> Generator[StateManager, None, None]:
@pytest_asyncio.fixture(
loop_scope="function", scope="function", params=["in_process", "disk", "redis"]
)
async def state_manager(request) -> AsyncGenerator[StateManager, None]:
"""Instance of state manager parametrized for redis and in-process.
Args:
@ -1621,7 +1629,7 @@ def state_manager(request) -> Generator[StateManager, None, None]:
yield state_manager
if isinstance(state_manager, StateManagerRedis):
asyncio.get_event_loop().run_until_complete(state_manager.close())
await state_manager.close()
@pytest.fixture()
@ -1709,8 +1717,8 @@ async def test_state_manager_contend(
assert not state_manager._states_locks[token].locked()
@pytest.fixture(scope="function")
def state_manager_redis() -> Generator[StateManager, None, None]:
@pytest_asyncio.fixture(loop_scope="function", scope="function")
async def state_manager_redis() -> AsyncGenerator[StateManager, None]:
"""Instance of state manager for redis only.
Yields:
@ -1723,7 +1731,7 @@ def state_manager_redis() -> Generator[StateManager, None, None]:
yield state_manager
asyncio.get_event_loop().run_until_complete(state_manager.close())
await state_manager.close()
@pytest.fixture()
@ -1883,11 +1891,11 @@ async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App):
async with sp:
assert sp._self_actx is not None
assert sp._self_mutable # proxy is mutable inside context
if isinstance(mock_app.state_manager, StateManagerMemory):
if isinstance(mock_app.state_manager, (StateManagerMemory, StateManagerDisk)):
# For in-process store, only one instance of the state exists
assert sp.__wrapped__ is grandchild_state
else:
# When redis or disk is used, a new+updated instance is assigned to the proxy
# When redis is used, a new+updated instance is assigned to the proxy
assert sp.__wrapped__ is not grandchild_state
sp.value2 = "42"
assert not sp._self_mutable # proxy is not mutable after exiting context
@ -1898,7 +1906,7 @@ async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App):
gotten_state = await mock_app.state_manager.get_state(
_substate_key(grandchild_state.router.session.client_token, grandchild_state)
)
if isinstance(mock_app.state_manager, StateManagerMemory):
if isinstance(mock_app.state_manager, (StateManagerMemory, StateManagerDisk)):
# For in-process store, only one instance of the state exists
assert gotten_state is parent_state
else:
@ -2789,6 +2797,9 @@ async def test_preprocess(app_module_mock, token, test_state, expected, mocker):
}
assert (await state._process(events[1]).__anext__()).delta == exp_is_hydrated(state)
if isinstance(app.state_manager, StateManagerRedis):
await app.state_manager.close()
@pytest.mark.asyncio
async def test_preprocess_multiple_load_events(app_module_mock, token, mocker):
@ -2836,6 +2847,9 @@ async def test_preprocess_multiple_load_events(app_module_mock, token, mocker):
}
assert (await state._process(events[2]).__anext__()).delta == exp_is_hydrated(state)
if isinstance(app.state_manager, StateManagerRedis):
await app.state_manager.close()
@pytest.mark.asyncio
async def test_get_state(mock_app: rx.App, token: str):
@ -2921,7 +2935,7 @@ async def test_get_state(mock_app: rx.App, token: str):
_substate_key(token, ChildState2)
)
assert isinstance(new_test_state, TestState)
if isinstance(mock_app.state_manager, StateManagerMemory):
if isinstance(mock_app.state_manager, (StateManagerMemory, StateManagerDisk)):
# In memory, it's the same instance
assert new_test_state is test_state
test_state._clean()
@ -2931,15 +2945,6 @@ async def test_get_state(mock_app: rx.App, token: str):
ChildState2.get_name(),
ChildState3.get_name(),
)
elif isinstance(mock_app.state_manager, StateManagerDisk):
# On disk, it's a new instance
assert new_test_state is not test_state
# All substates are available
assert tuple(sorted(new_test_state.substates)) == (
ChildState.get_name(),
ChildState2.get_name(),
ChildState3.get_name(),
)
else:
# With redis, we get a whole new instance
assert new_test_state is not test_state
@ -3263,3 +3268,45 @@ def test_child_mixin_state() -> None:
assert "computed" in ChildUsesMixinState.inherited_vars
assert "computed" not in ChildUsesMixinState.computed_vars
def test_assignment_to_undeclared_vars():
"""Test that an attribute error is thrown when undeclared vars are set."""
class State(BaseState):
val: str
_val: str
__val: str # type: ignore
def handle_supported_regular_vars(self):
self.val = "no underscore"
self._val = "single leading underscore"
self.__val = "double leading undercore"
def handle_regular_var(self):
self.num = 5
def handle_backend_var(self):
self._num = 5
def handle_non_var(self):
self.__num = 5
class Substate(State):
def handle_var(self):
self.value = 20
state = State() # type: ignore
sub_state = Substate() # type: ignore
with pytest.raises(SetUndefinedStateVarError):
state.handle_regular_var()
with pytest.raises(SetUndefinedStateVarError):
sub_state.handle_var()
with pytest.raises(SetUndefinedStateVarError):
state.handle_backend_var()
state.handle_supported_regular_vars()
state.handle_non_var()

View File

@ -1,9 +1,9 @@
"""Specialized test for a larger state tree."""
import asyncio
from typing import Generator
from typing import AsyncGenerator
import pytest
import pytest_asyncio
import reflex as rx
from reflex.state import BaseState, StateManager, StateManagerRedis, _substate_key
@ -210,8 +210,10 @@ ALWAYS_COMPUTED_DICT_KEYS = [
]
@pytest.fixture(scope="function")
def state_manager_redis(app_module_mock) -> Generator[StateManager, None, None]:
@pytest_asyncio.fixture(loop_scope="function", scope="function")
async def state_manager_redis(
app_module_mock,
) -> AsyncGenerator[StateManager, None]:
"""Instance of state manager for redis only.
Args:
@ -228,7 +230,7 @@ def state_manager_redis(app_module_mock) -> Generator[StateManager, None, None]:
yield state_manager
asyncio.get_event_loop().run_until_complete(state_manager.close())
await state_manager.close()
@pytest.mark.asyncio

View File

@ -52,4 +52,4 @@ def test_send(mocker, event):
telemetry._send(event, telemetry_enabled=True)
httpx_post_mock.assert_called_once()
pathlib_path_read_text_mock.assert_called_once()
assert pathlib_path_read_text_mock.call_count == 2

View File

@ -490,7 +490,7 @@ def test_var_indexing_str():
# Test that indexing gives a type of Var[str].
assert isinstance(str_var[0], Var)
assert str_var[0]._var_type == str
assert str_var[0]._var_type is str
# Test basic indexing.
assert str(str_var[0]) == "str.at(0)"
@ -623,7 +623,7 @@ def test_str_var_slicing():
# Test that slicing gives a type of Var[str].
assert isinstance(str_var[:1], Var)
assert str_var[:1]._var_type == str
assert str_var[:1]._var_type is str
# Test basic slicing.
assert str(str_var[:1]) == 'str.split("").slice(undefined, 1).join("")'

View File

@ -374,7 +374,7 @@ def test_format_match(
events=[EventSpec(handler=EventHandler(fn=mock_event))],
args_spec=lambda: [],
),
'((...args) => ((addEvents([(Event("mock_event", ({ })))], args, ({ })))))',
'((...args) => ((addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ })))))',
),
(
EventChain(
@ -395,7 +395,7 @@ def test_format_match(
],
args_spec=lambda e: [e.target.value],
),
'((_e) => ((addEvents([(Event("mock_event", ({ ["arg"] : _e["target"]["value"] })))], [_e], ({ })))))',
'((_e) => ((addEvents([(Event("mock_event", ({ ["arg"] : _e["target"]["value"] }), ({ })))], [_e], ({ })))))',
),
(
EventChain(
@ -403,7 +403,19 @@ def test_format_match(
args_spec=lambda: [],
event_actions={"stopPropagation": True},
),
'((...args) => ((addEvents([(Event("mock_event", ({ })))], args, ({ ["stopPropagation"] : true })))))',
'((...args) => ((addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ ["stopPropagation"] : true })))))',
),
(
EventChain(
events=[
EventSpec(
handler=EventHandler(fn=mock_event),
event_actions={"stopPropagation": True},
)
],
args_spec=lambda: [],
),
'((...args) => ((addEvents([(Event("mock_event", ({ }), ({ ["stopPropagation"] : true })))], args, ({ })))))',
),
(
EventChain(
@ -411,7 +423,7 @@ def test_format_match(
args_spec=lambda: [],
event_actions={"preventDefault": True},
),
'((...args) => ((addEvents([(Event("mock_event", ({ })))], args, ({ ["preventDefault"] : true })))))',
'((...args) => ((addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ ["preventDefault"] : true })))))',
),
({"a": "red", "b": "blue"}, '({ ["a"] : "red", ["b"] : "blue" })'),
(Var(_js_expr="var", _var_type=int).guess_type(), "var"),
@ -519,7 +531,7 @@ def test_format_event_handler(input, output):
[
(
EventSpec(handler=EventHandler(fn=mock_event)),
'(Event("mock_event", ({ })))',
'(Event("mock_event", ({ }), ({ })))',
),
],
)

View File

@ -96,7 +96,7 @@ class StrEnum(str, Enum):
BAR = "bar"
class TestEnum(Enum):
class FooBarEnum(Enum):
"""A lone enum class."""
FOO = "foo"
@ -151,10 +151,10 @@ class BaseSubclass(Base):
"key2": "prefix_bar",
},
),
(TestEnum.FOO, "foo"),
([TestEnum.FOO, TestEnum.BAR], ["foo", "bar"]),
(FooBarEnum.FOO, "foo"),
([FooBarEnum.FOO, FooBarEnum.BAR], ["foo", "bar"]),
(
{"key1": TestEnum.FOO, "key2": TestEnum.BAR},
{"key1": FooBarEnum.FOO, "key2": FooBarEnum.BAR},
{
"key1": "foo",
"key2": "bar",

View File

@ -117,7 +117,7 @@ def test_remove_existing_bun_installation(mocker):
Args:
mocker: Pytest mocker.
"""
mocker.patch("reflex.utils.prerequisites.os.path.exists", return_value=True)
mocker.patch("reflex.utils.prerequisites.Path.exists", return_value=True)
rm = mocker.patch("reflex.utils.prerequisites.path_ops.rm", mocker.Mock())
prerequisites.remove_existing_bun_installation()
@ -458,7 +458,7 @@ def test_bun_install_without_unzip(mocker):
mocker: Pytest mocker object.
"""
mocker.patch("reflex.utils.path_ops.which", return_value=None)
mocker.patch("os.path.exists", return_value=False)
mocker.patch("pathlib.Path.exists", return_value=False)
mocker.patch("reflex.utils.prerequisites.constants.IS_WINDOWS", False)
with pytest.raises(FileNotFoundError):
@ -476,7 +476,7 @@ def test_bun_install_version(mocker, bun_version):
"""
mocker.patch("reflex.utils.prerequisites.constants.IS_WINDOWS", False)
mocker.patch("os.path.exists", return_value=True)
mocker.patch("pathlib.Path.exists", return_value=True)
mocker.patch(
"reflex.utils.prerequisites.get_bun_version",
return_value=version.parse(bun_version),
@ -542,7 +542,9 @@ def test_style_prop_with_event_handler_value(callable):
style = {
"color": (
EventHandler(fn=callable) if type(callable) != EventHandler else callable
EventHandler(fn=callable)
if type(callable) is not EventHandler
else callable
)
}