Merge remote-tracking branch 'origin/main' into lendemor/add_sticky_logo

This commit is contained in:
Masen Furer 2025-01-23 16:53:13 -08:00
commit 8d58234db7
No known key found for this signature in database
GPG Key ID: B0008AD22B3B3A95
117 changed files with 1911 additions and 1844 deletions

View File

@ -81,15 +81,13 @@ jobs:
matrix:
# Show OS combos first in GUI
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8"]
python-version: ["3.10.16", "3.11.11", "3.12.8"]
exclude:
- os: windows-latest
python-version: "3.10.16"
- os: windows-latest
python-version: "3.9.21"
python-version: "3.11.11"
# keep only one python version for MacOS
- os: macos-latest
python-version: "3.9.21"
- os: macos-latest
python-version: "3.10.16"
- os: macos-latest
@ -98,7 +96,7 @@ jobs:
- os: windows-latest
python-version: "3.10.11"
- os: windows-latest
python-version: "3.9.13"
python-version: "3.11.9"
runs-on: ${{ matrix.os }}
steps:
@ -161,7 +159,11 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set up python
id: setup-python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
uses: snok/install-poetry@v1
with:

View File

@ -16,7 +16,7 @@ jobs:
- uses: ./.github/actions/setup_build_env
with:
python-version: "3.9.21"
python-version: '3.10'
run-poetry-install: true
create-venv-at-path: .venv
@ -55,7 +55,7 @@ jobs:
path: reflex-web
- name: Install Requirements for reflex-web
working-directory: ./reflex-web
run: poetry run uv pip install -r requirements.txt
run: poetry run uv pip install $(grep -ivE "reflex " requirements.txt)
- name: Install additional dependencies for DB access
run: poetry run uv pip install psycopg
- name: Init Website for reflex-web
@ -73,7 +73,7 @@ jobs:
echo "$outdated"
# Ignore 3rd party dependencies that are not updated.
filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images' || true)
filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images|ag-grid' || true)
no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)

View File

@ -50,14 +50,7 @@ jobs:
- run: poetry run uv pip install pyvirtualdisplay pillow pytest-split pytest-retry
- name: Run app harness tests
env:
SCREENSHOT_DIR: /tmp/screenshots/${{ matrix.state_manager }}/${{ matrix.python-version }}/${{ matrix.split_index }}
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
run: |
poetry run playwright install chromium
poetry run pytest tests/integration --retries 3 --maxfail=5 --splits 2 --group ${{matrix.split_index}}
- uses: actions/upload-artifact@v4
name: Upload failed test screenshots
if: always()
with:
name: failed_test_screenshots
path: /tmp/screenshots

View File

@ -43,22 +43,17 @@ jobs:
matrix:
# Show OS combos first in GUI
os: [ubuntu-latest, windows-latest]
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
# Windows is a bit behind on Python version availability in Github
python-version: ['3.10.16', '3.11.11', '3.12.8', '3.13.1']
exclude:
- os: windows-latest
python-version: "3.11.11"
- os: windows-latest
python-version: "3.10.16"
- os: windows-latest
python-version: "3.9.21"
python-version: '3.10.16'
include:
- os: windows-latest
python-version: "3.11.9"
- os: windows-latest
python-version: "3.10.11"
- os: windows-latest
python-version: "3.9.13"
python-version: '3.10.11'
runs-on: ${{ matrix.os }}
steps:

View File

@ -28,22 +28,18 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
python-version: ["3.10.16", "3.11.11", "3.12.8", "3.13.1"]
# Windows is a bit behind on Python version availability in Github
exclude:
- os: windows-latest
python-version: "3.11.11"
- os: windows-latest
python-version: "3.10.16"
- os: windows-latest
python-version: "3.9.21"
include:
- os: windows-latest
python-version: "3.11.9"
- os: windows-latest
python-version: "3.10.11"
- os: windows-latest
python-version: "3.9.13"
runs-on: ${{ matrix.os }}
# Service containers to run with `runner-job`
@ -92,8 +88,8 @@ jobs:
strategy:
fail-fast: false
matrix:
# Note: py39, py310, py311 versions chosen due to available arm64 darwin builds.
python-version: ["3.9.13", "3.10.11", "3.11.9", "3.12.8", "3.13.1"]
# Note: py310, py311 versions chosen due to available arm64 darwin builds.
python-version: ["3.10.11", "3.11.9", "3.12.8", "3.13.1"]
runs-on: macos-latest
steps:
- uses: actions/checkout@v4

View File

@ -28,7 +28,7 @@ repos:
entry: python3 scripts/make_pyi.py
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.313
rev: v1.1.334
hooks:
- id: pyright
args: [reflex, tests]

View File

@ -8,7 +8,7 @@ Here is a quick guide on how to run Reflex repo locally so you can start contrib
**Prerequisites:**
- Python >= 3.9
- Python >= 3.10
- Poetry version >= 1.4.0 and add it to your path (see [Poetry Docs](https://python-poetry.org/docs/#installation) for more info).
**1. Fork this repository:**
@ -87,7 +87,7 @@ poetry run ruff format .
```
Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and `make_pyi` will run automatically before each commit.
Note that pre-commit will only be installed when you use a Python version >= 3.9.
Note that pre-commit will only be installed when you use a Python version >= 3.10.
``` bash
pre-commit install

View File

@ -34,7 +34,7 @@ See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architectu
## ⚙️ Installation
Open a terminal and run (Requires Python 3.9+):
Open a terminal and run (Requires Python 3.10+):
```bash
pip install reflex

View File

@ -21,7 +21,7 @@ def get_package_size(venv_path: Path, os_name):
ValueError: when venv does not exist or python version is None.
"""
python_version = get_python_version(venv_path, os_name)
print("Python version:", python_version) # noqa: T201
print("Python version:", python_version)
if python_version is None:
raise ValueError("Error: Failed to determine Python version.")

View File

@ -122,7 +122,7 @@ def AppWithTenComponentsOnePage():
def index() -> rx.Component:
return rx.center(rx.vstack(*render_component(1)))
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
app.add_page(index)
@ -133,7 +133,7 @@ def AppWithHundredComponentOnePage():
def index() -> rx.Component:
return rx.center(rx.vstack(*render_component(100)))
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
app.add_page(index)
@ -144,7 +144,7 @@ def AppWithThousandComponentsOnePage():
def index() -> rx.Component:
return rx.center(rx.vstack(*render_component(1000)))
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
app.add_page(index)

View File

@ -162,7 +162,7 @@ def AppWithOnePage():
height="100vh",
)
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
app.add_page(index)
@ -170,7 +170,7 @@ def AppWithTenPages():
"""A reflex app with 10 pages."""
import reflex as rx
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
render_multiple_pages(app, 10)
@ -178,7 +178,7 @@ def AppWithHundredPages():
"""A reflex app with 100 pages."""
import reflex as rx
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
render_multiple_pages(app, 100)
@ -186,7 +186,7 @@ def AppWithThousandPages():
"""A reflex app with Thousand pages."""
import reflex as rx
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
render_multiple_pages(app, 1000)
@ -194,7 +194,7 @@ def AppWithTenThousandPages():
"""A reflex app with ten thousand pages."""
import reflex as rx
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
render_multiple_pages(app, 10000)

View File

@ -34,7 +34,7 @@ Auf unserer [Architektur-Seite](https://reflex.dev/blog/2024-03-21-reflex-archit
## ⚙️ Installation
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.9+):
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.10+):
```bash
pip install reflex

View File

@ -35,7 +35,7 @@ Consulta nuestra [página de arquitectura](https://reflex.dev/blog/2024-03-21-re
## ⚙️ Instalación
Abra un terminal y ejecute (Requiere Python 3.9+):
Abra un terminal y ejecute (Requiere Python 3.10+):
```bash
pip install reflex

View File

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

View File

@ -22,7 +22,7 @@
## ⚙️ Installazione
Apri un terminale ed esegui (Richiede Python 3.9+):
Apri un terminale ed esegui (Richiede Python 3.10+):
```bash
pip install reflex

View File

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

View File

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

View File

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

View File

@ -21,7 +21,7 @@
---
## ⚙️ Instalação
Abra um terminal e execute (Requer Python 3.9+):
Abra um terminal e execute (Requer Python 3.10+):
```bash
pip install reflex

View File

@ -24,7 +24,7 @@
## ⚙️ Kurulum
Bir terminal açın ve çalıştırın (Python 3.9+ gerekir):
Bir terminal açın ve çalıştırın (Python 3.10+ gerekir):
```bash
pip install reflex

View File

@ -34,7 +34,7 @@ Các tính năng chính:
## ⚙️ Cài đặt
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.9+):
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.10+):
```bash
pip install reflex

View File

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

View File

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

1676
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@ keywords = ["web", "framework"]
classifiers = ["Development Status :: 4 - Beta"]
[tool.poetry.dependencies]
python = "^3.9"
python = "^3.10"
fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
gunicorn = ">=20.1.0,<24.0"
jinja2 = ">=3.1.2,<4.0"
@ -50,7 +50,6 @@ httpx = ">=0.25.1,<1.0"
twine = ">=4.0.0,<7.0"
tomlkit = ">=0.12.4,<1.0"
lazy_loader = ">=0.4"
reflex-chakra = ">=0.6.0"
typing_extensions = ">=4.6.0"
[tool.poetry.group.dev.dependencies]
@ -86,15 +85,18 @@ build-backend = "poetry.core.masonry.api"
target-version = "py39"
output-format = "concise"
lint.isort.split-on-trailing-comma = false
lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "PERF", "PTH", "RUF", "SIM", "T", "TRY", "W"]
lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "N", "PERF", "PTH", "RUF", "SIM", "T", "TRY", "W"]
lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012", "TRY0"]
lint.pydocstyle.convention = "google"
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
"tests/*.py" = ["D100", "D103", "D104", "B018", "PERF", "T"]
"tests/*.py" = ["D100", "D103", "D104", "B018", "PERF", "T", "N"]
"benchmarks/*.py" = ["D100", "D103", "D104", "B018", "PERF", "T", "N"]
"reflex/.templates/*.py" = ["D100", "D103", "D104"]
"*.pyi" = ["D301", "D415", "D417", "D418", "E742"]
"*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N"]
"pyi_generator.py" = ["N802"]
"reflex/constants/*.py" = ["N"]
"*/blank.py" = ["I001"]
[tool.pytest.ini_options]
@ -102,5 +104,5 @@ asyncio_default_fixture_loop_scope = "function"
asyncio_mode = "auto"
[tool.codespell]
skip = "docs/*,*.html,examples/*, *.pyi"
skip = "docs/*,*.html,examples/*, *.pyi, poetry.lock"
ignore-words-list = "te, TreeE"

View File

@ -8,7 +8,7 @@ version = "0.0.1"
description = "Reflex custom component {{ module_name }}"
readme = "README.md"
license = { text = "Apache-2.0" }
requires-python = ">=3.9"
requires-python = ">=3.10"
authors = [{ name = "", email = "YOUREMAIL@domain.com" }]
keywords = ["reflex","reflex-custom-components"]

View File

@ -16,10 +16,7 @@ export default function RadixThemesColorModeProvider({ children }) {
if (isDevMode) {
const lastCompiledTimeInLocalStorage =
localStorage.getItem("last_compiled_time");
if (
lastCompiledTimeInLocalStorage &&
lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp
) {
if (lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp) {
// on app startup, make sure the application color mode is persisted correctly.
setTheme(defaultColorMode);
localStorage.setItem("last_compiled_time", lastCompiledTimeStamp);

View File

@ -408,7 +408,7 @@ export const connect = async (
socket.current = io(endpoint.href, {
path: endpoint["pathname"],
transports: transports,
protocols: env.TEST_MODE ? undefined : [reflexEnvironment.version],
protocols: [reflexEnvironment.version],
autoUnref: false,
});
// Ensure undefined fields in events are sent as null instead of removed

View File

@ -303,7 +303,6 @@ _MAPPING: dict = {
"event": [
"EventChain",
"EventHandler",
"background",
"call_script",
"call_function",
"run_script",
@ -367,19 +366,4 @@ getattr, __dir__, __all__ = lazy_loader.attach(
def __getattr__(name):
if name == "chakra":
from reflex.utils import console
console.deprecate(
"rx.chakra",
reason="and moved to a separate package. "
"To continue using Chakra UI components, install the `reflex-chakra` package via `pip install "
"reflex-chakra`.",
deprecation_version="0.6.0",
removal_version="0.7.0",
dedupe=True,
)
import reflex_chakra as rc
return rc
return getattr(name)

View File

@ -156,7 +156,6 @@ from .constants import Env as Env
from .constants.colors import Color as Color
from .event import EventChain as EventChain
from .event import EventHandler as EventHandler
from .event import background as background
from .event import call_function as call_function
from .event import call_script as call_script
from .event import clear_local_storage as clear_local_storage

View File

@ -251,36 +251,36 @@ class App(MiddlewareMixin, LifespanMixin):
# Attributes to add to the html root tag of every page.
html_custom_attrs: Optional[Dict[str, str]] = None
# A map from a route to an unevaluated page. PRIVATE.
unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
# A map from a route to an unevaluated page.
_unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
default_factory=dict
)
# A map from a page route to the component to render. Users should use `add_page`. PRIVATE.
pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
# A map from a page route to the component to render. Users should use `add_page`.
_pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
# The backend API object. PRIVATE.
api: FastAPI = None # type: ignore
# The backend API object.
_api: FastAPI | None = None
# The state class to use for the app. PRIVATE.
state: Optional[Type[BaseState]] = None
# The state class to use for the app.
_state: Optional[Type[BaseState]] = None
# Class to manage many client states.
_state_manager: Optional[StateManager] = None
# Mapping from a route to event handlers to trigger when the page loads. PRIVATE.
load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
# Mapping from a route to event handlers to trigger when the page loads.
_load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
default_factory=dict
)
# Admin dashboard to view and manage the database. PRIVATE.
# Admin dashboard to view and manage the database.
admin_dash: Optional[AdminDash] = None
# The async server name space. PRIVATE.
event_namespace: Optional[EventNamespace] = None
# The async server name space.
_event_namespace: Optional[EventNamespace] = None
# Background tasks that are currently running. PRIVATE.
background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
# Background tasks that are currently running.
_background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
# Frontend Error Handler Function
frontend_exception_handler: Callable[[Exception], None] = (
@ -292,6 +292,24 @@ class App(MiddlewareMixin, LifespanMixin):
[Exception], Union[EventSpec, List[EventSpec], None]
] = default_backend_exception_handler
@property
def api(self) -> FastAPI | None:
"""Get the backend api.
Returns:
The backend api.
"""
return self._api
@property
def event_namespace(self) -> EventNamespace | None:
"""Get the event namespace.
Returns:
The event namespace.
"""
return self._event_namespace
def __post_init__(self):
"""Initialize the app.
@ -311,7 +329,7 @@ class App(MiddlewareMixin, LifespanMixin):
set_breakpoints(self.style.pop("breakpoints"))
# Set up the API.
self.api = FastAPI(lifespan=self._run_lifespan_tasks)
self._api = FastAPI(lifespan=self._run_lifespan_tasks)
self._add_cors()
self._add_default_endpoints()
@ -334,8 +352,8 @@ class App(MiddlewareMixin, LifespanMixin):
def _enable_state(self) -> None:
"""Enable state for the app."""
if not self.state:
self.state = State
if not self._state:
self._state = State
self._setup_state()
def _setup_state(self) -> None:
@ -344,13 +362,13 @@ class App(MiddlewareMixin, LifespanMixin):
Raises:
RuntimeError: If the socket server is invalid.
"""
if not self.state:
if not self._state:
return
config = get_config()
# Set up the state manager.
self._state_manager = StateManager.create(state=self.state)
self._state_manager = StateManager.create(state=self._state)
# Set up the Socket.IO AsyncServer.
if not self.sio:
@ -381,12 +399,37 @@ class App(MiddlewareMixin, LifespanMixin):
namespace = config.get_event_namespace()
# Create the event namespace and attach the main app. Not related to any paths.
self.event_namespace = EventNamespace(namespace, self)
self._event_namespace = EventNamespace(namespace, self)
# Register the event namespace with the socket.
self.sio.register_namespace(self.event_namespace)
# Mount the socket app with the API.
self.api.mount(str(constants.Endpoint.EVENT), socket_app)
if self.api:
class HeaderMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
original_send = send
async def modified_send(message):
headers = dict(scope["headers"])
protocol_key = b"sec-websocket-protocol"
if (
message["type"] == "websocket.accept"
and protocol_key in headers
):
message["headers"] = [
*message.get("headers", []),
(b"sec-websocket-protocol", headers[protocol_key]),
]
return await original_send(message)
return await self.app(scope, receive, modified_send)
socket_app_with_headers = HeaderMiddleware(socket_app)
self.api.mount(str(constants.Endpoint.EVENT), socket_app_with_headers)
# Check the exception handlers
self._validate_exception_handlers()
@ -397,24 +440,35 @@ class App(MiddlewareMixin, LifespanMixin):
Returns:
The string representation of the app.
"""
return f"<App state={self.state.__name__ if self.state else None}>"
return f"<App state={self._state.__name__ if self._state else None}>"
def __call__(self) -> FastAPI:
"""Run the backend api instance.
Raises:
ValueError: If the app has not been initialized.
Returns:
The backend api.
"""
if not self.api:
raise ValueError("The app has not been initialized.")
return self.api
def _add_default_endpoints(self):
"""Add default api endpoints (ping)."""
# To test the server.
if not self.api:
return
self.api.get(str(constants.Endpoint.PING))(ping)
self.api.get(str(constants.Endpoint.HEALTH))(health)
def _add_optional_endpoints(self):
"""Add optional api endpoints (_upload)."""
if not self.api:
return
if Upload.is_used:
# To upload files.
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
@ -432,6 +486,8 @@ class App(MiddlewareMixin, LifespanMixin):
def _add_cors(self):
"""Add CORS middleware to the app."""
if not self.api:
return
self.api.add_middleware(
cors.CORSMiddleware,
allow_credentials=True,
@ -521,13 +577,13 @@ class App(MiddlewareMixin, LifespanMixin):
# Check if the route given is valid
verify_route_validity(route)
if route in self.unevaluated_pages and environment.RELOAD_CONFIG.is_set():
if route in self._unevaluated_pages and environment.RELOAD_CONFIG.is_set():
# when the app is reloaded(typically for app harness tests), we should maintain
# the latest render function of a route.This applies typically to decorated pages
# since they are only added when app._compile is called.
self.unevaluated_pages.pop(route)
self._unevaluated_pages.pop(route)
if route in self.unevaluated_pages:
if route in self._unevaluated_pages:
route_name = (
f"`{route}` or `/`"
if route == constants.PageNames.INDEX_ROUTE
@ -540,15 +596,15 @@ class App(MiddlewareMixin, LifespanMixin):
# Setup dynamic args for the route.
# this state assignment is only required for tests using the deprecated state kwarg for App
state = self.state if self.state else State
state = self._state if self._state else State
state.setup_dynamic_args(get_route_args(route))
if on_load:
self.load_events[route] = (
self._load_events[route] = (
on_load if isinstance(on_load, list) else [on_load]
)
self.unevaluated_pages[route] = UnevaluatedPage(
self._unevaluated_pages[route] = UnevaluatedPage(
component=component,
route=route,
title=title,
@ -563,10 +619,10 @@ class App(MiddlewareMixin, LifespanMixin):
Args:
route: The route of the page to compile.
save_page: If True, the compiled page is saved to self.pages.
save_page: If True, the compiled page is saved to self._pages.
"""
component, enable_state = compiler.compile_unevaluated_page(
route, self.unevaluated_pages[route], self.state, self.style, self.theme
route, self._unevaluated_pages[route], self._state, self.style, self.theme
)
if enable_state:
@ -575,7 +631,7 @@ class App(MiddlewareMixin, LifespanMixin):
# Add the page.
self._check_routes_conflict(route)
if save_page:
self.pages[route] = component
self._pages[route] = component
def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
"""Get the load events for a route.
@ -589,7 +645,7 @@ class App(MiddlewareMixin, LifespanMixin):
route = route.lstrip("/")
if route == "":
route = constants.PageNames.INDEX_ROUTE
return self.load_events.get(route, [])
return self._load_events.get(route, [])
def _check_routes_conflict(self, new_route: str):
"""Verify if there is any conflict between the new route and any existing route.
@ -613,7 +669,7 @@ class App(MiddlewareMixin, LifespanMixin):
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
)
for route in self.pages:
for route in self._pages:
replaced_route = replace_brackets_with_keywords(route)
for rw, r, nr in zip(
replaced_route.split("/"), route.split("/"), new_route.split("/")
@ -671,6 +727,9 @@ class App(MiddlewareMixin, LifespanMixin):
def _setup_admin_dash(self):
"""Setup the admin dash."""
# Get the admin dash.
if not self.api:
return
admin_dash = self.admin_dash
if admin_dash and admin_dash.models:
@ -775,10 +834,10 @@ class App(MiddlewareMixin, LifespanMixin):
def _setup_overlay_component(self):
"""If a State is not used and no overlay_component is specified, do not render the connection modal."""
if self.state is None and self.overlay_component is default_overlay_component:
if self._state is None and self.overlay_component is default_overlay_component:
self.overlay_component = None
for k, component in self.pages.items():
self.pages[k] = self._add_overlay_to_component(component)
for k, component in self._pages.items():
self._pages[k] = self._add_overlay_to_component(component)
def _add_error_boundary_to_component(self, component: Component) -> Component:
if self.error_boundary is None:
@ -790,14 +849,14 @@ class App(MiddlewareMixin, LifespanMixin):
def _setup_error_boundary(self):
"""If a State is not used and no error_boundary is specified, do not render the error boundary."""
if self.state is None and self.error_boundary is default_error_boundary:
if self._state is None and self.error_boundary is default_error_boundary:
self.error_boundary = None
for k, component in self.pages.items():
for k, component in self._pages.items():
# Skip the 404 page
if k == constants.Page404.SLUG:
continue
self.pages[k] = self._add_error_boundary_to_component(component)
self._pages[k] = self._add_error_boundary_to_component(component)
def _apply_decorated_pages(self):
"""Add @rx.page decorated pages to the app.
@ -823,11 +882,11 @@ class App(MiddlewareMixin, LifespanMixin):
Raises:
VarDependencyError: When a computed var has an invalid dependency.
"""
if not self.state:
if not self._state:
return
if not state:
state = self.state
state = self._state
for var in state.computed_vars.values():
if not var._cache:
@ -853,13 +912,13 @@ class App(MiddlewareMixin, LifespanMixin):
"""
from reflex.utils.exceptions import ReflexRuntimeError
self.pages = {}
self._pages = {}
def get_compilation_time() -> str:
return str(datetime.now().time()).split(".")[0]
# Render a default 404 page if the user didn't supply one
if constants.Page404.SLUG not in self.unevaluated_pages:
if constants.Page404.SLUG not in self._unevaluated_pages:
self.add_page(route=constants.Page404.SLUG)
# Fix up the style.
@ -877,7 +936,7 @@ class App(MiddlewareMixin, LifespanMixin):
should_compile = self._should_compile()
for route in self.unevaluated_pages:
for route in self._unevaluated_pages:
console.debug(f"Evaluating page: {route}")
self._compile_page(route, save_page=should_compile)
@ -904,7 +963,7 @@ class App(MiddlewareMixin, LifespanMixin):
progress.start()
task = progress.add_task(
f"[{get_compilation_time()}] Compiling:",
total=len(self.pages)
total=len(self._pages)
+ fixed_pages_within_executor
+ adhoc_steps_without_executor,
)
@ -923,7 +982,7 @@ class App(MiddlewareMixin, LifespanMixin):
# This has to happen before compiling stateful components as that
# prevents recursive functions from reaching all components.
for component in self.pages.values():
for component in self._pages.values():
# Add component._get_all_imports() to all_imports.
all_imports.update(component._get_all_imports())
@ -938,12 +997,12 @@ class App(MiddlewareMixin, LifespanMixin):
stateful_components_path,
stateful_components_code,
page_components,
) = compiler.compile_stateful_components(self.pages.values())
) = compiler.compile_stateful_components(self._pages.values())
progress.advance(task)
# Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
if code_uses_state_contexts(stateful_components_code) and self.state is None:
if code_uses_state_contexts(stateful_components_code) and self._state is None:
raise ReflexRuntimeError(
"To access rx.State in frontend components, at least one "
"subclass of rx.State must be defined in the app."
@ -980,10 +1039,10 @@ class App(MiddlewareMixin, LifespanMixin):
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
)
for route, component in zip(self.pages, page_components):
for route, component in zip(self._pages, page_components):
ExecutorSafeFunctions.COMPONENTS[route] = component
ExecutorSafeFunctions.STATE = self.state
ExecutorSafeFunctions.STATE = self._state
with executor:
result_futures = []
@ -993,7 +1052,7 @@ class App(MiddlewareMixin, LifespanMixin):
result_futures.append(f)
# Compile the pre-compiled pages.
for route in self.pages:
for route in self._pages:
_submit_work(
ExecutorSafeFunctions.compile_page,
route,
@ -1028,7 +1087,7 @@ class App(MiddlewareMixin, LifespanMixin):
# Compile the contexts.
compile_results.append(
compiler.compile_contexts(self.state, self.theme),
compiler.compile_contexts(self._state, self.theme),
)
if self.theme is not None:
# Fix #2992 by removing the top-level appearance prop
@ -1150,9 +1209,9 @@ class App(MiddlewareMixin, LifespanMixin):
)
task = asyncio.create_task(_coro())
self.background_tasks.add(task)
self._background_tasks.add(task)
# Clean up task from background_tasks set when complete.
task.add_done_callback(self.background_tasks.discard)
task.add_done_callback(self._background_tasks.discard)
return task
def _validate_exception_handlers(self):
@ -1162,11 +1221,11 @@ class App(MiddlewareMixin, LifespanMixin):
ValueError: If the custom exception handlers are invalid.
"""
FRONTEND_ARG_SPEC = {
frontend_arg_spec = {
"exception": Exception,
}
BACKEND_ARG_SPEC = {
backend_arg_spec = {
"exception": Exception,
}
@ -1174,8 +1233,8 @@ class App(MiddlewareMixin, LifespanMixin):
["frontend", "backend"],
[self.frontend_exception_handler, self.backend_exception_handler],
[
FRONTEND_ARG_SPEC,
BACKEND_ARG_SPEC,
frontend_arg_spec,
backend_arg_spec,
],
):
if hasattr(handler_fn, "__name__"):

View File

@ -12,7 +12,7 @@ from typing import Callable, Coroutine, Set, Union
from fastapi import FastAPI
from reflex.utils import console
from reflex.utils.exceptions import InvalidLifespanTaskType
from reflex.utils.exceptions import InvalidLifespanTaskTypeError
from .mixin import AppMixin
@ -64,10 +64,10 @@ class LifespanMixin(AppMixin):
task_kwargs: The kwargs of the task.
Raises:
InvalidLifespanTaskType: If the task is a generator function.
InvalidLifespanTaskTypeError: If the task is a generator function.
"""
if inspect.isgeneratorfunction(task) or inspect.isasyncgenfunction(task):
raise InvalidLifespanTaskType(
raise InvalidLifespanTaskTypeError(
f"Task {task.__name__} of type generator must be decorated with contextlib.asynccontextmanager."
)

View File

@ -5,14 +5,12 @@ Only the app attribute is explicitly exposed.
from concurrent.futures import ThreadPoolExecutor
from reflex import constants
from reflex.utils import telemetry
from reflex.utils.exec import is_prod_mode
from reflex.utils.prerequisites import get_and_validate_app
if constants.CompileVars.APP != "app":
raise AssertionError("unexpected variable name for 'app'")
telemetry.send("compile")
app, app_module = get_and_validate_app(reload=False)
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
# before compiling the app in a thread to avoid event loop error (REF-2172).
@ -31,6 +29,5 @@ del app_module
del compile_future
del get_and_validate_app
del is_prod_mode
del telemetry
del constants
del ThreadPoolExecutor

View File

@ -23,8 +23,6 @@ from typing import (
Union,
)
from typing_extensions import deprecated
import reflex.state
from reflex.base import Base
from reflex.compiler.templates import STATEFUL_COMPONENT
@ -47,11 +45,10 @@ from reflex.event import (
EventChain,
EventHandler,
EventSpec,
EventVar,
no_args_event_spec,
)
from reflex.style import Style, format_as_emotion
from reflex.utils import console, format, imports, types
from reflex.utils import format, imports, types
from reflex.utils.imports import (
ImmutableParsedImportDict,
ImportDict,
@ -547,41 +544,6 @@ class Component(BaseComponent, ABC):
# Construct the component.
super().__init__(*args, **kwargs)
@deprecated("Use rx.EventChain.create instead.")
def _create_event_chain(
self,
args_spec: types.ArgsSpec | Sequence[types.ArgsSpec],
value: Union[
Var,
EventHandler,
EventSpec,
List[Union[EventHandler, EventSpec, EventVar]],
Callable,
],
key: Optional[str] = None,
) -> Union[EventChain, Var]:
"""Create an event chain from a variety of input types.
Args:
args_spec: The args_spec of the event trigger being bound.
value: The value to create the event chain from.
key: The key of the event trigger being bound.
Returns:
The event chain.
"""
console.deprecate(
"Component._create_event_chain",
"Use rx.EventChain.create instead.",
deprecation_version="0.6.8",
removal_version="0.7.0",
)
return EventChain.create(
value=value, # type: ignore
args_spec=args_spec,
key=key,
)
def get_event_triggers(
self,
) -> Dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]]:

View File

@ -14,7 +14,7 @@ class Html(Div):
"""
# The HTML to render.
dangerouslySetInnerHTML: Var[Dict[str, str]]
dangerouslySetInnerHTML: Var[Dict[str, str]] # noqa: N815
@classmethod
def create(cls, *children, **props):

View File

@ -192,7 +192,7 @@ class GhostUpload(Fragment):
class Upload(MemoizationLeaf):
"""A file upload component."""
library = "react-dropzone@14.2.10"
library = "react-dropzone@14.3.5"
tag = ""

View File

@ -14,7 +14,7 @@ from reflex.components.radix.themes.layout.box import Box
from reflex.constants.colors import Color
from reflex.event import set_clipboard
from reflex.style import Style
from reflex.utils import console, format
from reflex.utils import format
from reflex.utils.imports import ImportVar
from reflex.vars.base import LiteralVar, Var, VarData
@ -382,7 +382,7 @@ for theme_name in dir(Theme):
class CodeBlock(Component, MarkdownComponentMap):
"""A code block."""
library = "react-syntax-highlighter@15.6.0"
library = "react-syntax-highlighter@15.6.1"
tag = "PrismAsyncLight"
@ -438,6 +438,8 @@ class CodeBlock(Component, MarkdownComponentMap):
can_copy = props.pop("can_copy", False)
copy_button = props.pop("copy_button", None)
# react-syntax-highlighter doesn't have an explicit "light" or "dark" theme so we use one-light and one-dark
# themes respectively to ensure code compatibility.
if "theme" not in props:
# Default color scheme responds to global color mode.
props["theme"] = color_mode_cond(
@ -445,17 +447,6 @@ class CodeBlock(Component, MarkdownComponentMap):
dark=Theme.one_dark,
)
# react-syntax-highlighter doesn't have an explicit "light" or "dark" theme so we use one-light and one-dark
# themes respectively to ensure code compatibility.
if "theme" in props and not isinstance(props["theme"], Var):
props["theme"] = getattr(Theme, format.to_snake_case(props["theme"])) # type: ignore
console.deprecate(
feature_name="theme prop as string",
reason="Use code_block.themes instead.",
deprecation_version="0.6.0",
removal_version="0.7.0",
)
if can_copy:
code = children[0]
copy_button = ( # type: ignore

View File

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Union
from reflex import constants
from reflex.utils import imports
from reflex.utils.exceptions import DynamicComponentMissingLibrary
from reflex.utils.exceptions import DynamicComponentMissingLibraryError
from reflex.utils.format import format_library_name
from reflex.utils.serializers import serializer
from reflex.vars import Var, get_unique_variable_name
@ -36,13 +36,15 @@ def bundle_library(component: Union["Component", str]):
component: The component to bundle the library with.
Raises:
DynamicComponentMissingLibrary: Raised when a dynamic component is missing a library.
DynamicComponentMissingLibraryError: Raised when a dynamic component is missing a library.
"""
if isinstance(component, str):
bundled_libraries.add(component)
return
if component.library is None:
raise DynamicComponentMissingLibrary("Component must have a library to bundle.")
raise DynamicComponentMissingLibraryError(
"Component must have a library to bundle."
)
bundled_libraries.add(format_library_name(component.library))

View File

@ -102,7 +102,7 @@ class Fieldset(Element):
name: Var[Union[str, int, bool]]
def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]:
"""Event handler spec for the on_submit event.
Returns:
@ -111,7 +111,7 @@ def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
return (FORM_DATA,)
def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]:
def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]:
"""Event handler spec for the on_submit event.
Returns:

View File

@ -270,8 +270,8 @@ class Fieldset(Element):
"""
...
def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]: ...
def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]: ...
def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]: ...
def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]: ...
class Form(BaseHTML):
@overload
@ -341,10 +341,10 @@ class Form(BaseHTML):
on_submit: Optional[
Union[
Union[
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
],
Union[
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
],
]
] = None,

View File

@ -3,11 +3,13 @@
from typing import Any, Literal, Optional, Union
from reflex.event import EventHandler, no_args_event_spec
from reflex.utils import types
from reflex.utils import console, types
from reflex.vars.base import Var
from .base import NextComponent
DEFAULT_W_H = "100%"
class Image(NextComponent):
"""Display an image."""
@ -53,7 +55,7 @@ class Image(NextComponent):
loading: Var[Literal["lazy", "eager"]]
# A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
blurDataURL: Var[str]
blur_data_url: Var[str]
# Fires when the image has loaded.
on_load: EventHandler[no_args_event_spec]
@ -80,8 +82,16 @@ class Image(NextComponent):
Returns:
_type_: _description_
"""
if "blurDataURL" in props:
console.deprecate(
feature_name="blurDataURL",
reason="Use blur_data_url instead",
deprecation_version="0.7.0",
removal_version="0.8.0",
)
props["blur_data_url"] = props.pop("blurDataURL")
style = props.get("style", {})
DEFAULT_W_H = "100%"
def check_prop_type(prop_name, prop_value):
if types.check_prop_in_allowed_types(prop_value, allowed_types=[int]):

View File

@ -11,6 +11,8 @@ from reflex.vars.base import Var
from .base import NextComponent
DEFAULT_W_H = "100%"
class Image(NextComponent):
@overload
@classmethod
@ -30,7 +32,7 @@ class Image(NextComponent):
loading: Optional[
Union[Literal["eager", "lazy"], Var[Literal["eager", "lazy"]]]
] = None,
blurDataURL: Optional[Union[Var[str], str]] = None,
blur_data_url: Optional[Union[Var[str], str]] = None,
style: Optional[Style] = None,
key: Optional[Any] = None,
id: Optional[Any] = None,
@ -71,7 +73,7 @@ class Image(NextComponent):
priority: When true, the image will be considered high priority and preload. Lazy loading is automatically disabled for images using priority.
placeholder: A placeholder to use while the image is loading. Possible values are blur, empty, or data:image/.... Defaults to empty.
loading: The loading behavior of the image. Defaults to lazy. Can hurt performance, recommended to use `priority` instead.
blurDataURL: A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
blur_data_url: A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
on_load: Fires when the image has loaded.
on_error: Fires when the image has an error.
style: The style of the component.

View File

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

View File

@ -485,11 +485,11 @@ to {
Returns:
The style of the component.
"""
slideDown = LiteralVar.create(
slide_down = LiteralVar.create(
"${slideDown} var(--animation-duration) var(--animation-easing)",
)
slideUp = LiteralVar.create(
slide_up = LiteralVar.create(
"${slideUp} var(--animation-duration) var(--animation-easing)",
)
@ -503,8 +503,8 @@ to {
"display": "block",
"height": "var(--space-3)",
},
"&[data-state='open']": {"animation": slideDown},
"&[data-state='closed']": {"animation": slideUp},
"&[data-state='open']": {"animation": slide_down},
"&[data-state='closed']": {"animation": slide_up},
_inherited_variant_selector("classic"): {
"color": "var(--accent-contrast)",
},

View File

@ -66,7 +66,7 @@ class DrawerRoot(DrawerComponent):
scroll_lock_timeout: Var[int]
# When `True`, it prevents scroll restoration. Defaults to `True`.
preventScrollRestoration: Var[bool]
prevent_scroll_restoration: Var[bool]
# Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
should_scale_background: Var[bool]

View File

@ -81,7 +81,7 @@ class DrawerRoot(DrawerComponent):
snap_points: Optional[List[Union[float, str]]] = None,
fade_from_index: Optional[Union[Var[int], int]] = None,
scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
preventScrollRestoration: Optional[Union[Var[bool], bool]] = None,
prevent_scroll_restoration: Optional[Union[Var[bool], bool]] = None,
should_scale_background: Optional[Union[Var[bool], bool]] = None,
close_threshold: Optional[Union[Var[float], float]] = None,
as_child: Optional[Union[Var[bool], bool]] = None,
@ -129,7 +129,7 @@ class DrawerRoot(DrawerComponent):
snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
preventScrollRestoration: When `True`, it prevents scroll restoration. Defaults to `True`.
prevent_scroll_restoration: When `True`, it prevents scroll restoration. Defaults to `True`.
should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
as_child: Change the default rendered element for the one passed as a child.
@ -567,7 +567,7 @@ class Drawer(ComponentNamespace):
snap_points: Optional[List[Union[float, str]]] = None,
fade_from_index: Optional[Union[Var[int], int]] = None,
scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
preventScrollRestoration: Optional[Union[Var[bool], bool]] = None,
prevent_scroll_restoration: Optional[Union[Var[bool], bool]] = None,
should_scale_background: Optional[Union[Var[bool], bool]] = None,
close_threshold: Optional[Union[Var[float], float]] = None,
as_child: Optional[Union[Var[bool], bool]] = None,
@ -615,7 +615,7 @@ class Drawer(ComponentNamespace):
snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
preventScrollRestoration: When `True`, it prevents scroll restoration. Defaults to `True`.
prevent_scroll_restoration: When `True`, it prevents scroll restoration. Defaults to `True`.
should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
as_child: Change the default rendered element for the one passed as a child.

View File

@ -132,10 +132,10 @@ class FormRoot(FormComponent, HTMLForm):
on_submit: Optional[
Union[
Union[
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
],
Union[
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
],
]
] = None,
@ -608,10 +608,10 @@ class Form(FormRoot):
on_submit: Optional[
Union[
Union[
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
],
Union[
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
],
]
] = None,
@ -741,10 +741,10 @@ class FormNamespace(ComponentNamespace):
on_submit: Optional[
Union[
Union[
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
],
Union[
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
],
]
] = None,

View File

@ -22,6 +22,8 @@ from ..base import (
LiteralButtonSize = Literal["1", "2", "3", "4"]
RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
"""A button designed specifically for usage with a single icon."""
@ -72,8 +74,6 @@ class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
"IconButton requires a child icon. Pass a string as the first child or a rx.icon."
)
if "size" in props:
RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
if isinstance(props["size"], str):
children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]]
else:

View File

@ -14,6 +14,7 @@ from reflex.vars.base import Var
from ..base import RadixLoadingProp, RadixThemesComponent
LiteralButtonSize = Literal["1", "2", "3", "4"]
RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
@overload

View File

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

View File

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

View File

@ -88,11 +88,13 @@ class ChartBase(RechartsCharts):
"width": width if width is not None else "100%",
"height": height if height is not None else "100%",
}
# Provide min dimensions so the graph always appears, even if the outer container is zero-size.
if width is None:
dim_props["min_width"] = 200
if height is None:
dim_props["min_height"] = 100
# Ensure that the min_height and min_width are set to prevent the chart from collapsing.
# We are using small values so that height and width can still be used over min_height and min_width.
# Without this, sometimes the chart will not be visible. Causing confusion to the user.
# With this, the user will see a small chart and can adjust the height and width and can figure out that the issue is with the size.
dim_props["min_height"] = props.pop("min_height", 10)
dim_props["min_width"] = props.pop("min_width", 10)
return ResponsiveContainer.create(
super().create(*children, **props),

View File

@ -36,11 +36,11 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
# The height of chart container. Can be a number or string. Default: "100%"
height: Var[Union[int, str]]
# The minimum width of chart container. Number
min_width: Var[int]
# The minimum width of chart container. Number or string.
min_width: Var[Union[int, str]]
# The minimum height of chart container. Number
min_height: Var[int]
# The minimum height of chart container. Number or string.
min_height: Var[Union[int, str]]
# If specified a positive number, debounced function will be used to handle the resize event. Default: 0
debounce: Var[int]
@ -250,10 +250,10 @@ class Cell(Recharts):
alias = "RechartsCell"
# The presentation attribute of a rectangle in bar or a sector in pie.
fill: Var[str]
fill: Var[str | Color]
# The presentation attribute of a rectangle in bar or a sector in pie.
stroke: Var[str]
stroke: Var[str | Color]
responsive_container = ResponsiveContainer.create

View File

@ -22,8 +22,8 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
aspect: Optional[Union[Var[int], int]] = None,
width: Optional[Union[Var[Union[int, str]], int, str]] = None,
height: Optional[Union[Var[Union[int, str]], int, str]] = None,
min_width: Optional[Union[Var[int], int]] = None,
min_height: Optional[Union[Var[int], int]] = None,
min_width: Optional[Union[Var[Union[int, str]], int, str]] = None,
min_height: Optional[Union[Var[Union[int, str]], int, str]] = None,
debounce: Optional[Union[Var[int], int]] = None,
style: Optional[Style] = None,
key: Optional[Any] = None,
@ -56,8 +56,8 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
aspect: The aspect ratio of the container. The final aspect ratio of the SVG element will be (width / height) * aspect. Number
width: The width of chart container. Can be a number or string. Default: "100%"
height: The height of chart container. Can be a number or string. Default: "100%"
min_width: The minimum width of chart container. Number
min_height: The minimum height of chart container. Number
min_width: The minimum width of chart container. Number or string.
min_height: The minimum height of chart container. Number or string.
debounce: If specified a positive number, debounced function will be used to handle the resize event. Default: 0
on_resize: If specified provides a callback providing the updated chart width and height values.
style: The style of the component.
@ -488,8 +488,8 @@ class Cell(Recharts):
def create( # type: ignore
cls,
*children,
fill: Optional[Union[Var[str], str]] = None,
stroke: Optional[Union[Var[str], str]] = None,
fill: Optional[Union[Color, Var[Union[Color, str]], str]] = None,
stroke: Optional[Union[Color, Var[Union[Color, str]], str]] = None,
style: Optional[Style] = None,
key: Optional[Any] = None,
id: Optional[Any] = None,

View File

@ -73,7 +73,7 @@ class Pie(Recharts):
data: Var[List[Dict[str, Any]]]
# Valid children components
_valid_children: List[str] = ["Cell", "LabelList"]
_valid_children: List[str] = ["Cell", "LabelList", "Bare"]
# Stoke color. Default: rx.color("accent", 9)
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))

View File

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

View File

@ -167,7 +167,7 @@ class ToastProps(PropsBase, NoExtrasAllowedProps):
class Toaster(Component):
"""A Toaster Component for displaying toast notifications."""
library: str = "sonner@1.7.1"
library: str = "sonner@1.7.2"
tag = "Toaster"

View File

@ -390,7 +390,7 @@ class EnvVar(Generic[T]):
os.environ[self.name] = str(value)
class env_var: # type: ignore
class env_var: # type: ignore # noqa: N801
"""Descriptor for environment variables."""
name: str
@ -556,9 +556,6 @@ class EnvironmentVariables:
# Arguments to pass to the app harness driver.
APP_HARNESS_DRIVER_ARGS: EnvVar[str] = env_var("")
# Where to save screenshots when tests fail.
SCREENSHOT_DIR: EnvVar[Optional[Path]] = env_var(None)
# Whether to check for outdated package versions.
REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True)
@ -826,16 +823,16 @@ class Config(Base):
if "api_url" not in self._non_default_attributes:
# If running in Github Codespaces, override API_URL
codespace_name = os.getenv("CODESPACE_NAME")
GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN = os.getenv(
github_codespaces_port_forwarding_domain = os.getenv(
"GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"
)
# If running on Replit.com interactively, override API_URL to ensure we maintain the backend_port
replit_dev_domain = os.getenv("REPLIT_DEV_DOMAIN")
backend_port = kwargs.get("backend_port", self.backend_port)
if codespace_name and GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:
if codespace_name and github_codespaces_port_forwarding_domain:
self.api_url = (
f"https://{codespace_name}-{kwargs.get('backend_port', self.backend_port)}"
f".{GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}"
f".{github_codespaces_port_forwarding_domain}"
)
elif replit_dev_domain and backend_port:
self.api_url = f"https://{replit_dev_domain}:{backend_port}"

View File

@ -28,6 +28,8 @@ class Ext(SimpleNamespace):
ZIP = ".zip"
# The extension for executable files on Windows.
EXE = ".exe"
# The extension for markdown files.
MD = ".md"
class CompileVars(SimpleNamespace):

View File

@ -37,10 +37,10 @@ class Bun(SimpleNamespace):
"""Bun constants."""
# The Bun version.
VERSION = "1.1.29"
VERSION = "1.2.0"
# Min Bun Version
MIN_VERSION = "0.7.0"
MIN_VERSION = "1.1.0"
# URL to bun install script.
INSTALL_URL = "https://raw.githubusercontent.com/reflex-dev/reflex/main/scripts/bun_install.sh"
@ -178,21 +178,21 @@ class PackageJson(SimpleNamespace):
PATH = "package.json"
DEPENDENCIES = {
"@babel/standalone": "7.26.0",
"@emotion/react": "11.13.3",
"axios": "1.7.7",
"@babel/standalone": "7.26.6",
"@emotion/react": "11.14.0",
"axios": "1.7.9",
"json5": "2.2.3",
"next": "15.1.4",
"next": "15.1.6",
"next-sitemap": "4.2.3",
"next-themes": "0.4.3",
"next-themes": "0.4.4",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-focus-lock": "2.13.2",
"react-focus-lock": "2.13.5",
"socket.io-client": "4.8.1",
"universal-cookie": "7.2.2",
}
DEV_DEPENDENCIES = {
"autoprefixer": "10.4.20",
"postcss": "8.4.49",
"postcss": "8.5.1",
"postcss-import": "16.1.0",
}

View File

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

View File

@ -25,7 +25,6 @@ from typing import (
overload,
)
import typing_extensions
from typing_extensions import (
Concatenate,
ParamSpec,
@ -40,7 +39,11 @@ from typing_extensions import (
from reflex import constants
from reflex.constants.state import FRONTEND_EVENT_STATE
from reflex.utils import console, format
from reflex.utils.exceptions import EventFnArgMismatch, EventHandlerArgTypeMismatch
from reflex.utils.exceptions import (
EventFnArgMismatchError,
EventHandlerArgTypeMismatchError,
MissingAnnotationError,
)
from reflex.utils.types import ArgsSpec, GenericType, typehint_issubclass
from reflex.vars import VarData
from reflex.vars.base import LiteralVar, Var
@ -96,32 +99,6 @@ _EVENT_FIELDS: set[str] = {f.name for f in dataclasses.fields(Event)}
BACKGROUND_TASK_MARKER = "_reflex_background_task"
def background(fn, *, __internal_reflex_call: bool = False):
"""Decorator to mark event handler as running in the background.
Args:
fn: The function to decorate.
Returns:
The same function, but with a marker set.
Raises:
TypeError: If the function is not a coroutine function or async generator.
"""
if not __internal_reflex_call:
console.deprecate(
"background-decorator",
"Use `rx.event(background=True)` instead.",
"0.6.5",
"0.7.0",
)
if not inspect.iscoroutinefunction(fn) and not inspect.isasyncgenfunction(fn):
raise TypeError("Background task must be async function or generator.")
setattr(fn, BACKGROUND_TASK_MARKER, True)
return fn
@dataclasses.dataclass(
init=True,
frozen=True,
@ -557,10 +534,10 @@ class JavasciptKeyboardEvent:
"""Interface for a Javascript KeyboardEvent https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent."""
key: str = ""
altKey: bool = False
ctrlKey: bool = False
metaKey: bool = False
shiftKey: bool = False
altKey: bool = False # noqa: N815
ctrlKey: bool = False # noqa: N815
metaKey: bool = False # noqa: N815
shiftKey: bool = False # noqa: N815
def input_event(e: Var[JavascriptInputEvent]) -> Tuple[Var[str]]:
@ -813,29 +790,10 @@ def server_side(name: str, sig: inspect.Signature, **kwargs) -> EventSpec:
)
@overload
def redirect(
path: str | Var[str],
is_external: Optional[bool] = None,
is_external: bool = False,
replace: bool = False,
) -> EventSpec: ...
@overload
@typing_extensions.deprecated("`external` is deprecated use `is_external` instead")
def redirect(
path: str | Var[str],
is_external: Optional[bool] = None,
replace: bool = False,
external: Optional[bool] = None,
) -> EventSpec: ...
def redirect(
path: str | Var[str],
is_external: Optional[bool] = None,
replace: bool = False,
external: Optional[bool] = None,
) -> EventSpec:
"""Redirect to a new path.
@ -843,26 +801,10 @@ def redirect(
path: The path to redirect to.
is_external: Whether to open in new tab or not.
replace: If True, the current page will not create a new history entry.
external(Deprecated): Whether to open in new tab or not.
Returns:
An event to redirect to the path.
"""
if external is not None:
console.deprecate(
"The `external` prop in `rx.redirect`",
"use `is_external` instead.",
"0.6.6",
"0.7.0",
)
# is_external should take precedence over external.
is_external = (
(False if external is None else external)
if is_external is None
else is_external
)
return server_side(
"_redirect",
get_fn_signature(redirect),
@ -1279,11 +1221,14 @@ def call_event_handler(
event_spec: The lambda that define the argument(s) to pass to the event handler.
key: The key to pass to the event handler.
Raises:
EventHandlerArgTypeMismatchError: If the event handler arguments do not match the event spec. #noqa: DAR402
TypeError: If the event handler arguments are invalid.
Returns:
The event spec from calling the event handler.
# noqa: DAR401 failure
#noqa: DAR401
"""
event_spec_args = parse_args_spec(event_spec) # type: ignore
@ -1320,10 +1265,15 @@ def call_event_handler(
),
)
)
type_match_found: dict[str, bool] = {}
delayed_exceptions: list[EventHandlerArgTypeMismatchError] = []
try:
type_hints_of_provided_callback = get_type_hints(event_callback.fn)
except NameError:
type_hints_of_provided_callback = {}
if event_spec_return_types:
failures = []
event_callback_spec = inspect.getfullargspec(event_callback.fn)
for event_spec_index, event_spec_return_type in enumerate(
@ -1335,43 +1285,35 @@ def call_event_handler(
arg if get_origin(arg) is not Var else get_args(arg)[0] for arg in args
]
try:
type_hints_of_provided_callback = get_type_hints(event_callback.fn)
except NameError:
type_hints_of_provided_callback = {}
failed_type_check = False
# check that args of event handler are matching the spec if type hints are provided
for i, arg in enumerate(event_callback_spec.args[1:]):
if arg not in type_hints_of_provided_callback:
continue
type_match_found.setdefault(arg, False)
try:
compare_result = typehint_issubclass(
args_types_without_vars[i], type_hints_of_provided_callback[arg]
)
except TypeError:
# TODO: In 0.7.0, remove this block and raise the exception
# raise TypeError(
# f"Could not compare types {args_types_without_vars[i]} and {type_hints_of_provided_callback[arg]} for argument {arg} of {event_handler.fn.__qualname__} provided for {key}." # noqa: ERA001
# ) from e
console.warn(
except TypeError as te:
raise TypeError(
f"Could not compare types {args_types_without_vars[i]} and {type_hints_of_provided_callback[arg]} for argument {arg} of {event_callback.fn.__qualname__} provided for {key}."
)
compare_result = False
) from te
if compare_result:
type_match_found[arg] = True
continue
else:
failure = EventHandlerArgTypeMismatch(
f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {type_hints_of_provided_callback[arg]} as annotated in {event_callback.fn.__qualname__} instead."
type_match_found[arg] = False
delayed_exceptions.append(
EventHandlerArgTypeMismatchError(
f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {type_hints_of_provided_callback[arg]} as annotated in {event_callback.fn.__qualname__} instead."
)
)
failures.append(failure)
failed_type_check = True
break
if not failed_type_check:
if all(type_match_found.values()):
delayed_exceptions.clear()
if event_spec_index:
args = get_args(event_spec_return_types[0])
@ -1393,15 +1335,10 @@ def call_event_handler(
f"Event handler {key} expects ({expect_string}) -> () but got ({given_string}) -> () as annotated in {event_callback.fn.__qualname__} instead. "
f"This may lead to unexpected behavior but is intentionally ignored for {key}."
)
return event_callback(*event_spec_args)
break
if failures:
console.deprecate(
"Mismatched event handler argument types",
"\n".join([str(f) for f in failures]),
"0.6.5",
"0.7.0",
)
if delayed_exceptions:
raise delayed_exceptions[0]
return event_callback(*event_spec_args) # type: ignore
@ -1420,26 +1357,26 @@ def unwrap_var_annotation(annotation: GenericType):
return annotation
def resolve_annotation(annotations: dict[str, Any], arg_name: str):
def resolve_annotation(annotations: dict[str, Any], arg_name: str, spec: ArgsSpec):
"""Resolve the annotation for the given argument name.
Args:
annotations: The annotations.
arg_name: The argument name.
spec: The specs which the annotations come from.
Raises:
MissingAnnotationError: If the annotation is missing for non-lambda methods.
Returns:
The resolved annotation.
"""
annotation = annotations.get(arg_name)
if annotation is None:
console.deprecate(
feature_name="Unannotated event handler arguments",
reason="Provide type annotations for event handler arguments.",
deprecation_version="0.6.3",
removal_version="0.7.0",
)
# Allow arbitrary attribute access two levels deep until removed.
return Dict[str, dict]
if not isinstance(spec, types.LambdaType):
raise MissingAnnotationError(var_name=arg_name)
else:
return dict[str, dict]
return annotation
@ -1461,7 +1398,13 @@ def parse_args_spec(arg_spec: ArgsSpec | Sequence[ArgsSpec]):
arg_spec(
*[
Var(f"_{l_arg}").to(
unwrap_var_annotation(resolve_annotation(annotations, l_arg))
unwrap_var_annotation(
resolve_annotation(
annotations,
l_arg,
spec=arg_spec,
)
)
)
for l_arg in spec.args
]
@ -1477,7 +1420,7 @@ def check_fn_match_arg_spec(
func_name: str | None = None,
):
"""Ensures that the function signature matches the passed argument specification
or raises an EventFnArgMismatch if they do not.
or raises an EventFnArgMismatchError if they do not.
Args:
user_func: The function to be validated.
@ -1487,7 +1430,7 @@ def check_fn_match_arg_spec(
func_name: The name of the function to be validated.
Raises:
EventFnArgMismatch: Raised if the number of mandatory arguments do not match
EventFnArgMismatchError: Raised if the number of mandatory arguments do not match
"""
user_args = inspect.getfullargspec(user_func).args
# Drop the first argument if it's a bound method
@ -1503,7 +1446,7 @@ def check_fn_match_arg_spec(
number_of_event_args = len(parsed_event_args)
if number_of_user_args - number_of_user_default_args > number_of_event_args:
raise EventFnArgMismatch(
raise EventFnArgMismatchError(
f"Event {key} only provides {number_of_event_args} arguments, but "
f"{func_name or user_func} requires at least {number_of_user_args - number_of_user_default_args} "
"arguments to be passed to the event handler.\n"
@ -1812,8 +1755,6 @@ V3 = TypeVar("V3")
V4 = TypeVar("V4")
V5 = TypeVar("V5")
background_event_decorator = background
class EventCallback(Generic[P, T]):
"""A descriptor that wraps a function to be used as an event."""
@ -1986,6 +1927,9 @@ class EventNamespace(types.SimpleNamespace):
func: The function to wrap.
background: Whether the event should be run in the background. Defaults to False.
Raises:
TypeError: If background is True and the function is not a coroutine or async generator. # noqa: DAR402
Returns:
The wrapped function.
"""
@ -1994,7 +1938,13 @@ class EventNamespace(types.SimpleNamespace):
func: Callable[Concatenate[BASE_STATE, P], T],
) -> EventCallback[P, T]:
if background is True:
return background_event_decorator(func, __internal_reflex_call=True) # type: ignore
if not inspect.iscoroutinefunction(
func
) and not inspect.isasyncgenfunction(func):
raise TypeError(
"Background task must be async function or generator."
)
setattr(func, BACKGROUND_TASK_MARKER, True)
return func # type: ignore
if func is not None:

View File

@ -9,7 +9,6 @@ from reflex.components.sonner.toast import toast as toast
from ..utils.console import warn
from . import hooks as hooks
from .assets import asset as asset
from .client_state import ClientStateVar as ClientStateVar
from .layout import layout as layout
from .misc import run_in_thread as run_in_thread
@ -62,7 +61,6 @@ class ExperimentalNamespace(SimpleNamespace):
_x = ExperimentalNamespace(
asset=asset,
client_state=ClientStateVar.create,
hooks=hooks,
layout=layout,

View File

@ -1,37 +0,0 @@
"""Helper functions for adding assets to the app."""
from typing import Optional
from reflex import assets
from reflex.utils import console
def asset(relative_filename: str, subfolder: Optional[str] = None) -> str:
"""DEPRECATED: use `rx.asset` with `shared=True` instead.
Add an asset to the app.
Place the file next to your including python file.
Copies the file to the app's external assets directory.
Example:
```python
rx.script(src=rx._x.asset("my_custom_javascript.js"))
rx.image(src=rx._x.asset("test_image.png","subfolder"))
```
Args:
relative_filename: The relative filename of the asset.
subfolder: The directory to place the asset in.
Returns:
The relative URL to the copied asset.
"""
console.deprecate(
feature_name="rx._x.asset",
reason="Use `rx.asset` with `shared=True` instead of `rx._x.asset`.",
deprecation_version="0.6.6",
removal_version="0.7.0",
)
return assets.asset(
relative_filename, shared=True, subfolder=subfolder, _stack_level=2
)

View File

@ -34,6 +34,18 @@ def _client_state_ref(var_name: str) -> str:
return f"refs['_client_state_{var_name}']"
def _client_state_ref_dict(var_name: str) -> str:
"""Get the ref path for a ClientStateVar.
Args:
var_name: The name of the variable.
Returns:
An accessor for ClientStateVar ref as a string.
"""
return f"refs['_client_state_dict_{var_name}']"
@dataclasses.dataclass(
eq=False,
frozen=True,
@ -115,10 +127,41 @@ class ClientStateVar(Var):
"react": [ImportVar(tag="useState"), ImportVar(tag="useId")],
}
if global_ref:
hooks[f"{_client_state_ref(var_name)} ??= {{}}"] = None
hooks[f"{_client_state_ref(setter_name)} ??= {{}}"] = None
hooks[f"{_client_state_ref(var_name)}[{id_name}] = {var_name}"] = None
hooks[f"{_client_state_ref(setter_name)}[{id_name}] = {setter_name}"] = None
arg_name = get_unique_variable_name()
func = ArgsFunctionOperationBuilder.create(
args_names=(arg_name,),
return_expr=Var("Array.prototype.forEach.call")
.to(FunctionVar)
.call(
(
Var("Object.values")
.to(FunctionVar)
.call(Var(_client_state_ref_dict(setter_name)))
.to(list)
.to(list)
)
+ Var.create(
[
Var(
f"(value) => {{ {_client_state_ref(var_name)} = value; }}"
)
]
).to(list),
ArgsFunctionOperationBuilder.create(
args_names=("setter",),
return_expr=Var("setter").to(FunctionVar).call(Var(arg_name)),
),
),
)
hooks[f"{_client_state_ref(setter_name)} = {func!s}"] = None
hooks[f"{_client_state_ref(var_name)} ??= {var_name!s}"] = None
hooks[f"{_client_state_ref_dict(var_name)} ??= {{}}"] = None
hooks[f"{_client_state_ref_dict(setter_name)} ??= {{}}"] = None
hooks[f"{_client_state_ref_dict(var_name)}[{id_name}] = {var_name}"] = None
hooks[
f"{_client_state_ref_dict(setter_name)}[{id_name}] = {setter_name}"
] = None
imports.update(_refs_import)
return cls(
_js_expr="",
@ -150,7 +193,7 @@ class ClientStateVar(Var):
return (
Var(
_js_expr=(
_client_state_ref(self._getter_name) + f"[{self._id_name}]"
_client_state_ref_dict(self._getter_name) + f"[{self._id_name}]"
if self._global_ref
else self._getter_name
),
@ -179,26 +222,11 @@ class ClientStateVar(Var):
"""
_var_data = VarData(imports=_refs_import if self._global_ref else {})
arg_name = get_unique_variable_name()
setter = (
ArgsFunctionOperationBuilder.create(
args_names=(arg_name,),
return_expr=Var("Array.prototype.forEach.call")
.to(FunctionVar)
.call(
Var("Object.values")
.to(FunctionVar)
.call(Var(_client_state_ref(self._setter_name))),
ArgsFunctionOperationBuilder.create(
args_names=("setter",),
return_expr=Var("setter").to(FunctionVar).call(Var(arg_name)),
),
),
_var_data=_var_data,
)
Var(_client_state_ref(self._setter_name))
if self._global_ref
else Var(self._setter_name, _var_data=_var_data).to(FunctionVar)
)
else Var(self._setter_name, _var_data=_var_data)
).to(FunctionVar)
if value is not NoValue:
# This is a hack to make it work like an EventSpec taking an arg

View File

@ -26,7 +26,7 @@ def const(name, value) -> Var:
return Var(_js_expr=f"const {name} = {value}")
def useCallback(func, deps) -> Var:
def useCallback(func, deps) -> Var: # noqa: N802
"""Create a useCallback hook with a function and dependencies.
Args:
@ -42,7 +42,7 @@ def useCallback(func, deps) -> Var:
)
def useContext(context) -> Var:
def useContext(context) -> Var: # noqa: N802
"""Create a useContext hook with a context.
Args:
@ -57,7 +57,7 @@ def useContext(context) -> Var:
)
def useRef(default) -> Var:
def useRef(default) -> Var: # noqa: N802
"""Create a useRef hook with a default value.
Args:
@ -72,7 +72,7 @@ def useRef(default) -> Var:
)
def useState(var_name, default=None) -> Var:
def useState(var_name, default=None) -> Var: # noqa: N802
"""Create a useState hook with a variable name and setter name.
Args:

View File

@ -109,7 +109,7 @@ class DrawerSidebar(DrawerRoot):
snap_points: Optional[List[Union[float, str]]] = None,
fade_from_index: Optional[Union[Var[int], int]] = None,
scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
preventScrollRestoration: Optional[Union[Var[bool], bool]] = None,
prevent_scroll_restoration: Optional[Union[Var[bool], bool]] = None,
should_scale_background: Optional[Union[Var[bool], bool]] = None,
close_threshold: Optional[Union[Var[float], float]] = None,
as_child: Optional[Union[Var[bool], bool]] = None,

View File

@ -306,6 +306,9 @@ def export(
help="Whether to exclude sqlite db files when exporting backend.",
hidden=True,
),
env: constants.Env = typer.Option(
constants.Env.PROD, help="The environment to export the app in."
),
loglevel: constants.LogLevel = typer.Option(
config.loglevel, help="The log level to use."
),
@ -323,6 +326,7 @@ def export(
backend=backend,
zip_dest_dir=zip_dest_dir,
upload_db_file=upload_db_file,
env=env,
loglevel=loglevel.subprocess_level(),
)
@ -501,6 +505,7 @@ def deploy(
),
):
"""Deploy the app to the Reflex hosting service."""
from reflex_cli.constants.base import LogLevel as HostingLogLevel
from reflex_cli.utils import dependency
from reflex_cli.v2 import cli as hosting_cli
@ -512,6 +517,21 @@ def deploy(
# Set the log level.
console.set_log_level(loglevel)
def convert_reflex_loglevel_to_reflex_cli_loglevel(
loglevel: constants.LogLevel,
) -> HostingLogLevel:
if loglevel == constants.LogLevel.DEBUG:
return HostingLogLevel.DEBUG
if loglevel == constants.LogLevel.INFO:
return HostingLogLevel.INFO
if loglevel == constants.LogLevel.WARNING:
return HostingLogLevel.WARNING
if loglevel == constants.LogLevel.ERROR:
return HostingLogLevel.ERROR
if loglevel == constants.LogLevel.CRITICAL:
return HostingLogLevel.CRITICAL
return HostingLogLevel.INFO
# Only check requirements if interactive.
# There is user interaction for requirements update.
if interactive:
@ -521,9 +541,7 @@ def deploy(
if prerequisites.needs_reinit(frontend=True):
_init(name=config.app_name, loglevel=loglevel)
prerequisites.check_latest_package_version(constants.ReflexHostingCLI.MODULE_NAME)
extra: dict[str, str] = (
{"config_path": config_path} if config_path is not None else {}
)
hosting_cli.deploy(
app_name=app_name,
app_id=app_id,
@ -547,15 +565,28 @@ def deploy(
envfile=envfile,
hostname=hostname,
interactive=interactive,
loglevel=type(loglevel).INFO, # type: ignore
loglevel=convert_reflex_loglevel_to_reflex_cli_loglevel(loglevel),
token=token,
project=project,
config_path=config_path,
project_name=project_name,
**extra,
**({"config_path": config_path} if config_path is not None else {}),
)
@cli.command()
def rename(
new_name: str = typer.Argument(..., help="The new name for the app."),
loglevel: constants.LogLevel = typer.Option(
config.loglevel, help="The log level to use."
),
):
"""Rename the app in the current directory."""
from reflex.utils import prerequisites
prerequisites.validate_app_name(new_name)
prerequisites.rename_app(new_name, loglevel)
cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")
cli.add_typer(

View File

@ -93,14 +93,14 @@ from reflex.event import (
)
from reflex.utils import console, format, path_ops, prerequisites, types
from reflex.utils.exceptions import (
ComputedVarShadowsBaseVars,
ComputedVarShadowsStateVar,
DynamicComponentInvalidSignature,
DynamicRouteArgShadowsStateVar,
EventHandlerShadowsBuiltInStateMethod,
ComputedVarShadowsBaseVarsError,
ComputedVarShadowsStateVarError,
DynamicComponentInvalidSignatureError,
DynamicRouteArgShadowsStateVarError,
EventHandlerShadowsBuiltInStateMethodError,
ImmutableStateError,
InvalidLockWarningThresholdError,
InvalidStateManagerMode,
InvalidStateManagerModeError,
LockExpiredError,
ReflexRuntimeError,
SetUndefinedStateVarError,
@ -815,7 +815,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
"""Check for shadow methods and raise error if any.
Raises:
EventHandlerShadowsBuiltInStateMethod: When an event handler shadows an inbuilt state method.
EventHandlerShadowsBuiltInStateMethodError: When an event handler shadows an inbuilt state method.
"""
overridden_methods = set()
state_base_functions = cls._get_base_functions()
@ -829,7 +829,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
overridden_methods.add(method.__name__)
for method_name in overridden_methods:
raise EventHandlerShadowsBuiltInStateMethod(
raise EventHandlerShadowsBuiltInStateMethodError(
f"The event handler name `{method_name}` shadows a builtin State method; use a different name instead"
)
@ -838,11 +838,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
"""Check for shadow base vars and raise error if any.
Raises:
ComputedVarShadowsBaseVars: When a computed var shadows a base var.
ComputedVarShadowsBaseVarsError: When a computed var shadows a base var.
"""
for computed_var_ in cls._get_computed_vars():
if computed_var_._js_expr in cls.__annotations__:
raise ComputedVarShadowsBaseVars(
raise ComputedVarShadowsBaseVarsError(
f"The computed var name `{computed_var_._js_expr}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead"
)
@ -851,14 +851,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
"""Check for shadow computed vars and raise error if any.
Raises:
ComputedVarShadowsStateVar: When a computed var shadows another.
ComputedVarShadowsStateVarError: When a computed var shadows another.
"""
for name, cv in cls.__dict__.items():
if not is_computed_var(cv):
continue
name = cv._js_expr
if name in cls.inherited_vars or name in cls.inherited_backend_vars:
raise ComputedVarShadowsStateVar(
raise ComputedVarShadowsStateVarError(
f"The computed var name `{cv._js_expr}` shadows a var in {cls.__module__}.{cls.__name__}; use a different name instead"
)
@ -1218,14 +1218,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
args: a dict of args
Raises:
DynamicRouteArgShadowsStateVar: If a dynamic arg is shadowing an existing var.
DynamicRouteArgShadowsStateVarError: If a dynamic arg is shadowing an existing var.
"""
for arg in args:
if (
arg in cls.computed_vars
and not isinstance(cls.computed_vars[arg], DynamicRouteVar)
) or arg in cls.base_vars:
raise DynamicRouteArgShadowsStateVar(
raise DynamicRouteArgShadowsStateVarError(
f"Dynamic route arg '{arg}' is shadowing an existing var in {cls.__module__}.{cls.__name__}"
)
for substate in cls.get_substates():
@ -1341,12 +1341,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
if field.allow_none and not is_optional(field_type):
field_type = Union[field_type, None]
if not _isinstance(value, field_type):
console.deprecate(
"mismatched-type-assignment",
f"Tried to assign value {value} of type {type(value)} to field {type(self).__name__}.{name} of type {field_type}."
" This might lead to unexpected behavior.",
"0.6.5",
"0.7.0",
console.error(
f"Expected field '{type(self).__name__}.{name}' to receive type '{field_type}',"
f" but got '{value}' of type '{type(value)}'."
)
# Set the attribute.
@ -1831,7 +1828,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
if (
isinstance(value, dict)
and inspect.isclass(hinted_args)
and not types.is_generic_alias(hinted_args) # py3.9-py3.10
and not types.is_generic_alias(hinted_args) # py3.10
):
if issubclass(hinted_args, Model):
# Remove non-fields from the payload
@ -2356,8 +2353,7 @@ def dynamic(func: Callable[[T], Component]):
The dynamically generated component.
Raises:
DynamicComponentInvalidSignature: If the function does not have exactly one parameter.
DynamicComponentInvalidSignature: If the function does not have a type hint for the state class.
DynamicComponentInvalidSignatureError: If the function does not have exactly one parameter or a type hint for the state class.
"""
number_of_parameters = len(inspect.signature(func).parameters)
@ -2369,12 +2365,12 @@ def dynamic(func: Callable[[T], Component]):
values = list(func_signature.values())
if number_of_parameters != 1:
raise DynamicComponentInvalidSignature(
raise DynamicComponentInvalidSignatureError(
"The function must have exactly one parameter, which is the state class."
)
if len(values) != 1:
raise DynamicComponentInvalidSignature(
raise DynamicComponentInvalidSignatureError(
"You must provide a type hint for the state class in the function."
)
@ -2878,7 +2874,7 @@ class StateManager(Base, ABC):
state: The state class to use.
Raises:
InvalidStateManagerMode: If the state manager mode is invalid.
InvalidStateManagerModeError: If the state manager mode is invalid.
Returns:
The state manager (either disk, memory or redis).
@ -2901,7 +2897,7 @@ class StateManager(Base, ABC):
lock_expiration=config.redis_lock_expiration,
lock_warning_threshold=config.redis_lock_warning_threshold,
)
raise InvalidStateManagerMode(
raise InvalidStateManagerModeError(
f"Expected one of: DISK, MEMORY, REDIS, got {config.state_manager_mode}"
)
@ -4059,10 +4055,10 @@ def serialize_mutable_proxy(mp: MutableProxy):
return mp.__wrapped__
_orig_json_JSONEncoder_default = json.JSONEncoder.default
_orig_json_encoder_default = json.JSONEncoder.default
def _json_JSONEncoder_default_wrapper(self: json.JSONEncoder, o: Any) -> Any:
def _json_encoder_default_wrapper(self: json.JSONEncoder, o: Any) -> Any:
"""Wrap JSONEncoder.default to handle MutableProxy objects.
Args:
@ -4076,10 +4072,10 @@ def _json_JSONEncoder_default_wrapper(self: json.JSONEncoder, o: Any) -> Any:
return o.__wrapped__
except AttributeError:
pass
return _orig_json_JSONEncoder_default(self, o)
return _orig_json_encoder_default(self, o)
json.JSONEncoder.default = _json_JSONEncoder_default_wrapper
json.JSONEncoder.default = _json_encoder_default_wrapper
class ImmutableMutableProxy(MutableProxy):

View File

@ -287,7 +287,9 @@ class Style(dict):
_var = LiteralVar.create(value)
if _var is not None:
# Carry the imports/hooks when setting a Var as a value.
self._var_data = VarData.merge(self._var_data, _var._get_all_var_data())
self._var_data = VarData.merge(
getattr(self, "_var_data", None), _var._get_all_var_data()
)
super().__setitem__(key, value)

View File

@ -87,7 +87,7 @@ else:
# borrowed from py3.11
class chdir(contextlib.AbstractContextManager):
class chdir(contextlib.AbstractContextManager): # noqa: N801
"""Non thread-safe context manager to change the current working directory."""
def __init__(self, path):
@ -296,7 +296,7 @@ class AppHarness:
self.app_instance = self.app_module.app
if isinstance(self.app_instance._state_manager, StateManagerRedis):
# Create our own redis connection for testing.
self.state_manager = StateManagerRedis.create(self.app_instance.state)
self.state_manager = StateManagerRedis.create(self.app_instance._state)
else:
self.state_manager = self.app_instance._state_manager
@ -324,7 +324,7 @@ class AppHarness:
return _shutdown_redis
def _start_backend(self, port=0):
if self.app_instance is None:
if self.app_instance is None or self.app_instance.api is None:
raise RuntimeError("App was not initialized.")
self.backend = uvicorn.Server(
uvicorn.Config(
@ -353,12 +353,12 @@ class AppHarness:
self.app_instance.state_manager,
StateManagerRedis,
)
and self.app_instance.state is not None
and self.app_instance._state is not None
):
with contextlib.suppress(RuntimeError):
await self.app_instance.state_manager.close()
self.app_instance._state_manager = StateManagerRedis.create(
state=self.app_instance.state,
state=self.app_instance._state,
)
if not isinstance(self.app_instance.state_manager, StateManagerRedis):
raise RuntimeError("Failed to reset state manager.")
@ -933,6 +933,7 @@ class AppHarnessProd(AppHarness):
frontend=True,
backend=False,
loglevel=reflex.constants.LogLevel.INFO,
env=reflex.constants.Env.PROD,
)
self.frontend_thread = threading.Thread(target=self._run_frontend)

View File

@ -42,10 +42,7 @@ def codespaces_port_forwarding_domain() -> str | None:
Returns:
The domain for port forwarding in Github Codespaces, or None if not running in Codespaces.
"""
GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN = os.getenv(
"GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"
)
return GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN
return os.getenv("GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN")
def is_running_in_codespaces() -> bool:

View File

@ -51,20 +51,12 @@ def set_log_level(log_level: LogLevel):
log_level: The log level to set.
Raises:
ValueError: If the log level is invalid.
TypeError: If the log level is a string.
"""
if not isinstance(log_level, LogLevel):
deprecate(
feature_name="Passing a string to set_log_level",
reason="use reflex.constants.LogLevel enum instead",
deprecation_version="0.6.6",
removal_version="0.7.0",
raise TypeError(
f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead."
)
try:
log_level = getattr(LogLevel, log_level.upper())
except AttributeError as ae:
raise ValueError(f"Invalid log level: {log_level}") from ae
global _LOG_LEVEL
_LOG_LEVEL = log_level

View File

@ -1,6 +1,6 @@
"""Custom Exceptions."""
from typing import Any, NoReturn
from typing import Any
class ReflexError(Exception):
@ -11,7 +11,7 @@ class ConfigError(ReflexError):
"""Custom exception for config related errors."""
class InvalidStateManagerMode(ReflexError, ValueError):
class InvalidStateManagerModeError(ReflexError, ValueError):
"""Raised when an invalid state manager mode is provided."""
@ -75,6 +75,30 @@ class VarAttributeError(ReflexError, AttributeError):
"""Custom AttributeError for var related errors."""
class UntypedComputedVarError(ReflexError, TypeError):
"""Custom TypeError for untyped computed var errors."""
def __init__(self, var_name):
"""Initialize the UntypedComputedVarError.
Args:
var_name: The name of the computed var.
"""
super().__init__(f"Computed var '{var_name}' must have a type annotation.")
class MissingAnnotationError(ReflexError, TypeError):
"""Custom TypeError for missing annotations."""
def __init__(self, var_name):
"""Initialize the MissingAnnotationError.
Args:
var_name: The name of the var.
"""
super().__init__(f"Var '{var_name}' must have a type annotation.")
class UploadValueError(ReflexError, ValueError):
"""Custom ValueError for upload related errors."""
@ -111,43 +135,43 @@ class MatchTypeError(ReflexError, TypeError):
"""Raised when the return types of match cases are different."""
class EventHandlerArgTypeMismatch(ReflexError, TypeError):
class EventHandlerArgTypeMismatchError(ReflexError, TypeError):
"""Raised when the annotations of args accepted by an EventHandler differs from the spec of the event trigger."""
class EventFnArgMismatch(ReflexError, TypeError):
class EventFnArgMismatchError(ReflexError, TypeError):
"""Raised when the number of args required by an event handler is more than provided by the event trigger."""
class DynamicRouteArgShadowsStateVar(ReflexError, NameError):
class DynamicRouteArgShadowsStateVarError(ReflexError, NameError):
"""Raised when a dynamic route arg shadows a state var."""
class ComputedVarShadowsStateVar(ReflexError, NameError):
class ComputedVarShadowsStateVarError(ReflexError, NameError):
"""Raised when a computed var shadows a state var."""
class ComputedVarShadowsBaseVars(ReflexError, NameError):
class ComputedVarShadowsBaseVarsError(ReflexError, NameError):
"""Raised when a computed var shadows a base var."""
class EventHandlerShadowsBuiltInStateMethod(ReflexError, NameError):
class EventHandlerShadowsBuiltInStateMethodError(ReflexError, NameError):
"""Raised when an event handler shadows a built-in state method."""
class GeneratedCodeHasNoFunctionDefs(ReflexError):
class GeneratedCodeHasNoFunctionDefsError(ReflexError):
"""Raised when refactored code generated with flexgen has no functions defined."""
class PrimitiveUnserializableToJSON(ReflexError, ValueError):
class PrimitiveUnserializableToJSONError(ReflexError, ValueError):
"""Raised when a primitive type is unserializable to JSON. Usually with NaN and Infinity."""
class InvalidLifespanTaskType(ReflexError, TypeError):
class InvalidLifespanTaskTypeError(ReflexError, TypeError):
"""Raised when an invalid task type is registered as a lifespan task."""
class DynamicComponentMissingLibrary(ReflexError, ValueError):
class DynamicComponentMissingLibraryError(ReflexError, ValueError):
"""Raised when a dynamic component is missing a library."""
@ -163,7 +187,7 @@ class EnvironmentVarValueError(ReflexError, ValueError):
"""Raised when an environment variable is set to an invalid value."""
class DynamicComponentInvalidSignature(ReflexError, TypeError):
class DynamicComponentInvalidSignatureError(ReflexError, TypeError):
"""Raised when a dynamic component has an invalid signature."""
@ -186,29 +210,27 @@ class StateMismatchError(ReflexError, ValueError):
class SystemPackageMissingError(ReflexError):
"""Raised when a system package is missing."""
def __init__(self, package: str):
"""Initialize the SystemPackageMissingError.
Args:
package: The missing package.
"""
from reflex.constants import IS_MACOS
extra = (
f" You can do so by running 'brew install {package}'." if IS_MACOS else ""
)
super().__init__(
f"System package '{package}' is missing."
f" Please install it through your system package manager.{extra}"
)
class EventDeserializationError(ReflexError, ValueError):
"""Raised when an event cannot be deserialized."""
def raise_system_package_missing_error(package: str) -> NoReturn:
"""Raise a SystemPackageMissingError.
Args:
package: The name of the missing system package.
Raises:
SystemPackageMissingError: The raised exception.
"""
from reflex.constants import IS_MACOS
raise SystemPackageMissingError(
f"System package '{package}' is missing."
" Please install it through your system package manager."
+ (f" You can do so by running 'brew install {package}'." if IS_MACOS else "")
)
class InvalidLockWarningThresholdError(ReflexError):
"""Raised when an invalid lock warning threshold is provided."""

View File

@ -240,25 +240,28 @@ def run_backend(
run_uvicorn_backend(host, port, loglevel)
def get_reload_dirs() -> list[str]:
def get_reload_dirs() -> list[Path]:
"""Get the reload directories for the backend.
Returns:
The reload directories for the backend.
"""
config = get_config()
reload_dirs = [config.app_name]
reload_dirs = [Path(config.app_name)]
if config.app_module is not None and config.app_module.__file__:
module_path = Path(config.app_module.__file__).resolve().parent
while module_path.parent.name:
for parent_file in module_path.parent.iterdir():
if parent_file == "__init__.py":
# go up a level to find dir without `__init__.py`
module_path = module_path.parent
break
if any(
sibling_file.name == "__init__.py"
for sibling_file in module_path.parent.iterdir()
):
# go up a level to find dir without `__init__.py`
module_path = module_path.parent
else:
break
reload_dirs.append(str(module_path))
reload_dirs = [module_path]
return reload_dirs
@ -278,7 +281,7 @@ def run_uvicorn_backend(host, port, loglevel: LogLevel):
port=port,
log_level=loglevel.value,
reload=True,
reload_dirs=get_reload_dirs(),
reload_dirs=list(map(str, get_reload_dirs())),
)
@ -361,11 +364,11 @@ def run_uvicorn_backend_prod(host, port, loglevel):
app_module = get_app_module()
RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split()
RUN_BACKEND_PROD_WINDOWS = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split()
run_backend_prod = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split()
run_backend_prod_windows = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split()
command = (
[
*RUN_BACKEND_PROD_WINDOWS,
*run_backend_prod_windows,
"--host",
host,
"--port",
@ -374,7 +377,7 @@ def run_uvicorn_backend_prod(host, port, loglevel):
]
if constants.IS_WINDOWS
else [
*RUN_BACKEND_PROD,
*run_backend_prod,
"--bind",
f"{host}:{port}",
"--threads",
@ -526,48 +529,3 @@ def is_prod_mode() -> bool:
"""
current_mode = environment.REFLEX_ENV_MODE.get()
return current_mode == constants.Env.PROD
def is_frontend_only() -> bool:
"""Check if the app is running in frontend-only mode.
Returns:
True if the app is running in frontend-only mode.
"""
console.deprecate(
"is_frontend_only() is deprecated and will be removed in a future release.",
reason="Use `environment.REFLEX_FRONTEND_ONLY.get()` instead.",
deprecation_version="0.6.5",
removal_version="0.7.0",
)
return environment.REFLEX_FRONTEND_ONLY.get()
def is_backend_only() -> bool:
"""Check if the app is running in backend-only mode.
Returns:
True if the app is running in backend-only mode.
"""
console.deprecate(
"is_backend_only() is deprecated and will be removed in a future release.",
reason="Use `environment.REFLEX_BACKEND_ONLY.get()` instead.",
deprecation_version="0.6.5",
removal_version="0.7.0",
)
return environment.REFLEX_BACKEND_ONLY.get()
def should_skip_compile() -> bool:
"""Whether the app should skip compile.
Returns:
True if the app should skip compile.
"""
console.deprecate(
"should_skip_compile() is deprecated and will be removed in a future release.",
reason="Use `environment.REFLEX_SKIP_COMPILE.get()` instead.",
deprecation_version="0.6.5",
removal_version="0.7.0",
)
return environment.REFLEX_SKIP_COMPILE.get()

View File

@ -4,7 +4,7 @@ from pathlib import Path
from typing import Optional
from reflex import constants
from reflex.config import get_config
from reflex.config import environment, get_config
from reflex.utils import build, console, exec, prerequisites, telemetry
config = get_config()
@ -18,6 +18,7 @@ def export(
upload_db_file: bool = False,
api_url: Optional[str] = None,
deploy_url: Optional[str] = None,
env: constants.Env = constants.Env.PROD,
loglevel: constants.LogLevel = console._LOG_LEVEL,
):
"""Export the app to a zip file.
@ -30,11 +31,15 @@ def export(
upload_db_file: Whether to upload the database file. Defaults to False.
api_url: The API URL to use. Defaults to None.
deploy_url: The deploy URL to use. Defaults to None.
env: The environment to use. Defaults to constants.Env.PROD.
loglevel: The log level to use. Defaults to console._LOG_LEVEL.
"""
# Set the log level.
console.set_log_level(loglevel)
# Set env mode in the environment
environment.REFLEX_ENV_MODE.set(env)
# Override the config url values if provided.
if api_url is not None:
config.api_url = str(api_url)

View File

@ -11,7 +11,6 @@ from typing import TYPE_CHECKING, Any, List, Optional, Union
from reflex import constants
from reflex.constants.state import FRONTEND_EVENT_STATE
from reflex.utils import exceptions
from reflex.utils.console import deprecate
if TYPE_CHECKING:
from reflex.components.component import ComponentStyle
@ -502,37 +501,6 @@ if TYPE_CHECKING:
from reflex.vars import Var
def format_event_chain(
event_chain: EventChain | Var[EventChain],
event_arg: Var | None = None,
) -> str:
"""DEPRECATED: format an event chain as a javascript invocation.
Use str(rx.Var.create(event_chain)) instead.
Args:
event_chain: The event chain to format.
event_arg: this argument is ignored.
Returns:
Compiled javascript code to queue the given event chain on the frontend.
"""
deprecate(
feature_name="format_event_chain",
reason="Use str(rx.Var.create(event_chain)) instead",
deprecation_version="0.6.0",
removal_version="0.7.0",
)
from reflex.vars import Var
from reflex.vars.function import ArgsFunctionOperation
result = Var.create(event_chain)
if isinstance(result, ArgsFunctionOperation):
result = result._return_expr
return str(result)
def format_queue_events(
events: EventType | None = None,
args_spec: Optional[ArgsSpec] = None,

View File

@ -7,6 +7,7 @@ import dataclasses
import functools
import importlib
import importlib.metadata
import importlib.util
import json
import os
import platform
@ -37,8 +38,8 @@ from reflex.compiler import templates
from reflex.config import Config, environment, get_config
from reflex.utils import console, net, path_ops, processes, redir
from reflex.utils.exceptions import (
GeneratedCodeHasNoFunctionDefs,
raise_system_package_missing_error,
GeneratedCodeHasNoFunctionDefsError,
SystemPackageMissingError,
)
from reflex.utils.format import format_library_name
from reflex.utils.registry import _get_npm_registry
@ -86,18 +87,6 @@ def get_web_dir() -> Path:
return environment.REFLEX_WEB_WORKDIR.get()
def _python_version_check():
"""Emit deprecation warning for deprecated python versions."""
# Check for end-of-life python versions.
if sys.version_info < (3, 10):
console.deprecate(
feature_name="Support for Python 3.9 and older",
reason="please upgrade to Python 3.10 or newer",
deprecation_version="0.6.0",
removal_version="0.7.0",
)
def check_latest_package_version(package_name: str):
"""Check if the latest version of the package is installed.
@ -120,8 +109,6 @@ def check_latest_package_version(package_name: str):
console.warn(
f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
)
# Check for deprecated python versions
_python_version_check()
except Exception:
pass
@ -477,6 +464,167 @@ def validate_app_name(app_name: str | None = None) -> str:
return app_name
def rename_path_up_tree(full_path: str | Path, old_name: str, new_name: str) -> Path:
"""Rename all instances of `old_name` in the path (file and directories) to `new_name`.
The renaming stops when we reach the directory containing `rxconfig.py`.
Args:
full_path: The full path to start renaming from.
old_name: The name to be replaced.
new_name: The replacement name.
Returns:
The updated path after renaming.
"""
current_path = Path(full_path)
new_path = None
while True:
directory, base = current_path.parent, current_path.name
# Stop renaming when we reach the root dir (which contains rxconfig.py)
if current_path.is_dir() and (current_path / "rxconfig.py").exists():
new_path = current_path
break
if old_name == base.removesuffix(constants.Ext.PY):
new_base = base.replace(old_name, new_name)
new_path = directory / new_base
current_path.rename(new_path)
console.debug(f"Renamed {current_path} -> {new_path}")
current_path = new_path
else:
new_path = current_path
# Move up the directory tree
current_path = directory
return new_path
def rename_app(new_app_name: str, loglevel: constants.LogLevel):
"""Rename the app directory.
Args:
new_app_name: The new name for the app.
loglevel: The log level to use.
Raises:
Exit: If the command is not ran in the root dir or the app module cannot be imported.
"""
# Set the log level.
console.set_log_level(loglevel)
if not constants.Config.FILE.exists():
console.error(
"No rxconfig.py found. Make sure you are in the root directory of your app."
)
raise typer.Exit(1)
sys.path.insert(0, str(Path.cwd()))
config = get_config()
module_path = importlib.util.find_spec(config.module)
if module_path is None:
console.error(f"Could not find module {config.module}.")
raise typer.Exit(1)
if not module_path.origin:
console.error(f"Could not find origin for module {config.module}.")
raise typer.Exit(1)
console.info(f"Renaming app directory to {new_app_name}.")
process_directory(
Path.cwd(),
config.app_name,
new_app_name,
exclude_dirs=[constants.Dirs.WEB, constants.Dirs.APP_ASSETS],
)
rename_path_up_tree(Path(module_path.origin), config.app_name, new_app_name)
console.success(f"App directory renamed to [bold]{new_app_name}[/bold].")
def rename_imports_and_app_name(file_path: str | Path, old_name: str, new_name: str):
"""Rename imports the file using string replacement as well as app_name in rxconfig.py.
Args:
file_path: The file to process.
old_name: The old name to replace.
new_name: The new name to use.
"""
file_path = Path(file_path)
content = file_path.read_text()
# Replace `from old_name.` or `from old_name` with `from new_name`
content = re.sub(
rf"\bfrom {re.escape(old_name)}(\b|\.|\s)",
lambda match: f"from {new_name}{match.group(1)}",
content,
)
# Replace `import old_name` with `import new_name`
content = re.sub(
rf"\bimport {re.escape(old_name)}\b",
f"import {new_name}",
content,
)
# Replace `app_name="old_name"` in rx.Config
content = re.sub(
rf'\bapp_name\s*=\s*["\']{re.escape(old_name)}["\']',
f'app_name="{new_name}"',
content,
)
# Replace positional argument `"old_name"` in rx.Config
content = re.sub(
rf'\brx\.Config\(\s*["\']{re.escape(old_name)}["\']',
f'rx.Config("{new_name}"',
content,
)
file_path.write_text(content)
def process_directory(
directory: str | Path,
old_name: str,
new_name: str,
exclude_dirs: list | None = None,
extensions: list | None = None,
):
"""Process files with specified extensions in a directory, excluding specified directories.
Args:
directory: The root directory to process.
old_name: The old name to replace.
new_name: The new name to use.
exclude_dirs: List of directory names to exclude. Defaults to None.
extensions: List of file extensions to process.
"""
exclude_dirs = exclude_dirs or []
extensions = extensions or [
constants.Ext.PY,
constants.Ext.MD,
] # include .md files, typically used in reflex-web.
extensions_set = {ext.lstrip(".") for ext in extensions}
directory = Path(directory)
root_exclude_dirs = {directory / exclude_dir for exclude_dir in exclude_dirs}
files = (
p.resolve()
for p in directory.glob("**/*")
if p.is_file() and p.suffix.lstrip(".") in extensions_set
)
for file_path in files:
if not any(
file_path.is_relative_to(exclude_dir) for exclude_dir in root_exclude_dirs
):
rename_imports_and_app_name(file_path, old_name, new_name)
def create_config(app_name: str):
"""Create a new rxconfig file.
@ -885,7 +1033,11 @@ def install_node():
def install_bun():
"""Install bun onto the user's system."""
"""Install bun onto the user's system.
Raises:
SystemPackageMissingError: If "unzip" is missing.
"""
win_supported = is_windows_bun_supported()
one_drive_in_path = windows_check_onedrive_in_path()
if constants.IS_WINDOWS and (not win_supported or one_drive_in_path):
@ -924,13 +1076,14 @@ def install_bun():
else:
unzip_path = path_ops.which("unzip")
if unzip_path is None:
raise_system_package_missing_error("unzip")
raise SystemPackageMissingError("unzip")
# Run the bun install script.
download_and_run(
constants.Bun.INSTALL_URL,
f"bun-v{constants.Bun.VERSION}",
BUN_INSTALL=str(constants.Bun.ROOT_PATH),
BUN_VERSION=str(constants.Bun.VERSION),
)
@ -1663,7 +1816,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
generation_hash: The generation hash from reflex.build.
Raises:
GeneratedCodeHasNoFunctionDefs: If the fetched code has no function definitions
GeneratedCodeHasNoFunctionDefsError: If the fetched code has no function definitions
(the refactored reflex code is expected to have at least one root function defined).
"""
# Download the reflex code for the generation.
@ -1680,7 +1833,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
# Determine the name of the last function, which renders the generated code.
defined_funcs = re.findall(r"def ([a-zA-Z_]+)\(", resp.text)
if not defined_funcs:
raise GeneratedCodeHasNoFunctionDefs(
raise GeneratedCodeHasNoFunctionDefsError(
f"No function definitions found in generated code from {url!r}."
)
render_func_name = defined_funcs[-1]

View File

@ -122,7 +122,7 @@ def _prepare_event(event: str, **kwargs) -> dict:
return {}
if UTC is None:
# for python 3.9 & 3.10
# for python 3.10
stamp = datetime.utcnow().isoformat()
else:
# for python 3.11 & 3.12

View File

@ -46,6 +46,7 @@ from reflex.base import Base
from reflex.constants.compiler import Hooks
from reflex.utils import console, exceptions, imports, serializers, types
from reflex.utils.exceptions import (
UntypedComputedVarError,
VarAttributeError,
VarDependencyError,
VarTypeError,
@ -545,52 +546,21 @@ class Var(Generic[VAR_TYPE]):
def create(
cls,
value: Any,
_var_is_local: bool | None = None,
_var_is_string: bool | None = None,
_var_data: VarData | None = None,
) -> Var:
"""Create a var from a value.
Args:
value: The value to create the var from.
_var_is_local: Whether the var is local. Deprecated.
_var_is_string: Whether the var is a string literal. Deprecated.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The var.
"""
if _var_is_local is not None:
console.deprecate(
feature_name="_var_is_local",
reason="The _var_is_local argument is not supported for Var. "
"If you want to create a Var from a raw Javascript expression, use the constructor directly",
deprecation_version="0.6.0",
removal_version="0.7.0",
)
if _var_is_string is not None:
console.deprecate(
feature_name="_var_is_string",
reason="The _var_is_string argument is not supported for Var. "
"If you want to create a Var from a raw Javascript expression, use the constructor directly",
deprecation_version="0.6.0",
removal_version="0.7.0",
)
# If the value is already a var, do nothing.
if isinstance(value, Var):
return value
# Try to pull the imports and hooks from contained values.
if not isinstance(value, str):
return LiteralVar.create(value, _var_data=_var_data)
if _var_is_string is False or _var_is_local is True:
return cls(
_js_expr=value,
_var_data=_var_data,
)
return LiteralVar.create(value, _var_data=_var_data)
@classmethod
@ -1585,7 +1555,7 @@ def figure_out_type(value: Any) -> types.GenericType:
return type(value)
class cached_property_no_lock(functools.cached_property):
class cached_property_no_lock(functools.cached_property): # noqa: N801
"""A special version of functools.cached_property that does not use a lock."""
def __init__(self, func):
@ -1863,19 +1833,14 @@ class ComputedVar(Var[RETURN_TYPE]):
Raises:
TypeError: If the computed var dependencies are not Var instances or var names.
UntypedComputedVarError: If the computed var is untyped.
"""
hint = kwargs.pop("return_type", None) or get_type_hints(fget).get(
"return", Any
)
if hint is Any:
console.deprecate(
"untyped-computed-var",
"ComputedVar should have a return type annotation.",
"0.6.5",
"0.7.0",
)
raise UntypedComputedVarError(var_name=fget.__name__)
kwargs.setdefault("_js_expr", fget.__name__)
kwargs.setdefault("_var_type", hint)
@ -1948,6 +1913,7 @@ class ComputedVar(Var[RETURN_TYPE]):
"_var_data": kwargs.pop(
"_var_data", VarData.merge(self._var_data, merge_var_data)
),
"return_type": kwargs.pop("return_type", self._var_type),
}
if kwargs:
@ -2082,12 +2048,9 @@ class ComputedVar(Var[RETURN_TYPE]):
value = getattr(instance, self._cache_attr)
if not _isinstance(value, self._var_type):
console.deprecate(
"mismatched-computed-var-return",
f"Computed var {type(instance).__name__}.{self._js_expr} returned value of type {type(value)}, "
f"expected {self._var_type}. This might cause unexpected behavior.",
"0.6.5",
"0.7.0",
console.error(
f"Computed var '{type(instance).__name__}.{self._js_expr}' must return"
f" type '{self._var_type}', got '{type(value)}'."
)
return value

View File

@ -18,7 +18,7 @@ from typing import (
)
from reflex.constants.base import Dirs
from reflex.utils.exceptions import PrimitiveUnserializableToJSON, VarTypeError
from reflex.utils.exceptions import PrimitiveUnserializableToJSONError, VarTypeError
from reflex.utils.imports import ImportDict, ImportVar
from .base import (
@ -987,10 +987,10 @@ class LiteralNumberVar(LiteralVar, NumberVar):
The JSON representation of the var.
Raises:
PrimitiveUnserializableToJSON: If the var is unserializable to JSON.
PrimitiveUnserializableToJSONError: If the var is unserializable to JSON.
"""
if math.isinf(self._var_value) or math.isnan(self._var_value):
raise PrimitiveUnserializableToJSON(
raise PrimitiveUnserializableToJSONError(
f"No valid JSON representation for {self}"
)
return json.dumps(self._var_value)

View File

@ -78,6 +78,14 @@ case $platform in
;;
esac
case "$target" in
'linux'*)
if [ -f /etc/alpine-release ]; then
target="$target-musl"
fi
;;
esac
if [[ $target = darwin-x64 ]]; then
# Is this process running in Rosetta?
# redirect stderr to devnull to avoid error message when not running in Rosetta
@ -91,19 +99,20 @@ GITHUB=${GITHUB-"https://github.com"}
github_repo="$GITHUB/oven-sh/bun"
if [[ $target = darwin-x64 ]]; then
# If AVX2 isn't supported, use the -baseline build
# If AVX2 isn't supported, use the -baseline build
case "$target" in
'darwin-x64'*)
if [[ $(sysctl -a | grep machdep.cpu | grep AVX2) == '' ]]; then
target=darwin-x64-baseline
target="$target-baseline"
fi
fi
if [[ $target = linux-x64 ]]; then
;;
'linux-x64'*)
# If AVX2 isn't supported, use the -baseline build
if [[ $(cat /proc/cpuinfo | grep avx2) = '' ]]; then
target=linux-x64-baseline
target="$target-baseline"
fi
fi
;;
esac
exe_name=bun
@ -113,8 +122,10 @@ if [[ $# = 2 && $2 = debug-info ]]; then
info "You requested a debug build of bun. More information will be shown if a crash occurs."
fi
bun_version=BUN_VERSION
if [[ $# = 0 ]]; then
bun_uri=$github_repo/releases/latest/download/bun-$target.zip
bun_uri=$github_repo/releases/download/bun-v$bun_version/bun-$target.zip
else
bun_uri=$github_repo/releases/download/$1/bun-$target.zip
fi

View File

@ -214,8 +214,12 @@ function Install-Bun {
# http://community.sqlbackupandftp.com/t/error-1073741515-solved/1305
if (($LASTEXITCODE -eq 3221225781) -or ($LASTEXITCODE -eq -1073741515)) # STATUS_DLL_NOT_FOUND
{
# TODO: as of July 2024, Bun has no external dependencies.
# I want to keep this error message in for a few months to ensure that
# if someone somehow runs into this, it can be reported.
Write-Output "Install Failed - You are missing a DLL required to run bun.exe"
Write-Output "This can be solved by installing the Visual C++ Redistributable from Microsoft:`nSee https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist`nDirect Download -> https://aka.ms/vs/17/release/vc_redist.x64.exe`n`n"
Write-Output "The error above should be unreachable as Bun does not depend on this library. Please comment in https://github.com/oven-sh/bun/issues/8598 or open a new issue.`n`n"
Write-Output "The command '${BunBin}\bun.exe --revision' exited with code ${LASTEXITCODE}`n"
return 1
}

View File

@ -1,8 +1,6 @@
"""Shared conftest for all integration tests."""
import os
import re
from pathlib import Path
import pytest
@ -36,34 +34,6 @@ def xvfb():
yield None
def pytest_exception_interact(node, call, report):
"""Take and upload screenshot when tests fail.
Args:
node: The pytest item that failed.
call: The pytest call describing when/where the test was invoked.
report: The pytest log report object.
"""
screenshot_dir = environment.SCREENSHOT_DIR.get()
if DISPLAY is None or screenshot_dir is None:
return
screenshot_dir = Path(screenshot_dir)
screenshot_dir.mkdir(parents=True, exist_ok=True)
safe_filename = re.sub(
r"(?u)[^-\w.]",
"_",
str(node.nodeid).strip().replace(" ", "_").replace(":", "_").replace(".py", ""),
)
try:
DISPLAY.waitgrab().save(
(Path(screenshot_dir) / safe_filename).with_suffix(".png"),
)
except Exception as e:
print(f"Failed to take screenshot for {node}: {e}")
@pytest.fixture(
scope="session", params=[AppHarness, AppHarnessProd], ids=["dev", "prod"]
)

View File

@ -1,4 +1,4 @@
FROM python:3.9
FROM python:3.10
ARG USERNAME=kerrigan
RUN useradd -m $USERNAME

View File

@ -172,7 +172,7 @@ def BackgroundTask():
rx.button("Reset", on_click=State.reset_counter, id="reset"),
)
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
app.add_page(index)
@ -288,7 +288,7 @@ def test_background_task(
assert background_task._poll_for(lambda: counter.text == "620", timeout=40)
# all tasks should have exited and cleaned up
assert background_task._poll_for(
lambda: not background_task.app_instance.background_tasks # type: ignore
lambda: not background_task.app_instance._background_tasks # type: ignore
)

View File

@ -16,7 +16,7 @@ from .utils import SessionStorage
def CallScript():
"""A test app for browser javascript integration."""
from pathlib import Path
from typing import Dict, List, Optional, Union
from typing import Optional, Union
import reflex as rx
@ -43,15 +43,17 @@ def CallScript():
external_scripts = inline_scripts.replace("inline", "external")
class CallScriptState(rx.State):
results: List[Optional[Union[str, Dict, List]]] = []
inline_counter: int = 0
external_counter: int = 0
results: rx.Field[list[Optional[Union[str, dict, list]]]] = rx.field([])
inline_counter: rx.Field[int] = rx.field(0)
external_counter: rx.Field[int] = rx.field(0)
value: str = "Initial"
last_result: str = ""
last_result: int = 0
@rx.event
def call_script_callback(self, result):
self.results.append(result)
@rx.event
def call_script_callback_other_arg(self, result, other_arg):
self.results.append([other_arg, result])
@ -91,7 +93,7 @@ def CallScript():
def call_script_inline_return_lambda(self):
return rx.call_script(
"inline2()",
callback=lambda result: CallScriptState.call_script_callback_other_arg( # type: ignore
callback=lambda result: CallScriptState.call_script_callback_other_arg(
result, "lambda"
),
)
@ -100,7 +102,7 @@ def CallScript():
def get_inline_counter(self):
return rx.call_script(
"inline_counter",
callback=CallScriptState.set_inline_counter, # type: ignore
callback=CallScriptState.setvar("inline_counter"),
)
@rx.event
@ -139,7 +141,7 @@ def CallScript():
def call_script_external_return_lambda(self):
return rx.call_script(
"external2()",
callback=lambda result: CallScriptState.call_script_callback_other_arg( # type: ignore
callback=lambda result: CallScriptState.call_script_callback_other_arg(
result, "lambda"
),
)
@ -148,28 +150,28 @@ def CallScript():
def get_external_counter(self):
return rx.call_script(
"external_counter",
callback=CallScriptState.set_external_counter, # type: ignore
callback=CallScriptState.setvar("external_counter"),
)
@rx.event
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
callback=CallScriptState.setvar("last_result"),
)
@rx.event
def call_with_var_str_cast(self):
return rx.call_script(
f"{rx.Var('inline_counter')!s} + {rx.Var('external_counter')!s}",
callback=CallScriptState.set_last_result, # type: ignore
callback=CallScriptState.setvar("last_result"),
)
@rx.event
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
callback=CallScriptState.setvar("last_result"),
)
@rx.event
@ -178,7 +180,7 @@ def CallScript():
rx.Var(
f"{rx.Var('inline_counter')!s} + {rx.Var('external_counter')!s}"
),
callback=CallScriptState.set_last_result, # type: ignore
callback=CallScriptState.setvar("last_result"),
)
@rx.event
@ -186,24 +188,24 @@ def CallScript():
yield rx.call_script("inline_counter = 0; external_counter = 0")
self.reset()
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
Path("assets/external.js").write_text(external_scripts)
@app.add_page
def index():
return rx.vstack(
rx.input(
value=CallScriptState.inline_counter.to(str), # type: ignore
value=CallScriptState.inline_counter.to(str),
id="inline_counter",
read_only=True,
),
rx.input(
value=CallScriptState.external_counter.to(str), # type: ignore
value=CallScriptState.external_counter.to(str),
id="external_counter",
read_only=True,
),
rx.text_area(
value=CallScriptState.results.to_string(), # type: ignore
value=CallScriptState.results.to_string(),
id="results",
read_only=True,
),
@ -273,7 +275,7 @@ def CallScript():
CallScriptState.value,
on_click=rx.call_script(
"'updated'",
callback=CallScriptState.set_value, # type: ignore
callback=CallScriptState.setvar("value"),
),
id="update_value",
),
@ -282,7 +284,7 @@ def CallScript():
value=CallScriptState.last_result,
id="last_result",
read_only=True,
on_click=CallScriptState.set_last_result(""), # type: ignore
on_click=CallScriptState.setvar("last_result", 0),
),
rx.button(
"call_with_var_f_string",
@ -308,7 +310,7 @@ def CallScript():
"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
callback=CallScriptState.setvar("last_result"),
),
id="call_with_var_f_string_inline",
),
@ -316,7 +318,7 @@ def CallScript():
"call_with_var_str_cast_inline",
on_click=rx.call_script(
f"{rx.Var('inline_counter')!s} + {rx.Var('external_counter')!s}",
callback=CallScriptState.set_last_result, # type: ignore
callback=CallScriptState.setvar("last_result"),
),
id="call_with_var_str_cast_inline",
),
@ -326,7 +328,7 @@ def CallScript():
rx.Var(
f"{rx.Var('inline_counter')} + {CallScriptState.last_result}"
),
callback=CallScriptState.set_last_result, # type: ignore
callback=CallScriptState.setvar("last_result"),
),
id="call_with_var_f_string_wrapped_inline",
),
@ -336,7 +338,7 @@ def CallScript():
rx.Var(
f"{rx.Var('inline_counter')!s} + {rx.Var('external_counter')!s}"
),
callback=CallScriptState.set_last_result, # type: ignore
callback=CallScriptState.setvar("last_result"),
),
id="call_with_var_str_cast_wrapped_inline",
),
@ -483,7 +485,7 @@ def test_call_script_w_var(
"""
assert_token(driver)
last_result = driver.find_element(By.ID, "last_result")
assert last_result.get_attribute("value") == ""
assert last_result.get_attribute("value") == "0"
inline_return_button = driver.find_element(By.ID, "inline_return")

View File

@ -33,18 +33,18 @@ def ClientSide():
class ClientSideSubState(ClientSideState):
# cookies with default settings
c1: str = rx.Cookie()
c2: rx.Cookie = "c2 default" # type: ignore
c2: str = rx.Cookie("c2 default")
# cookies with custom settings
c3: str = rx.Cookie(max_age=2) # expires after 2 second
c4: rx.Cookie = rx.Cookie(same_site="strict")
c4: str = rx.Cookie(same_site="strict")
c5: str = rx.Cookie(path="/foo/") # only accessible on `/foo/`
c6: str = rx.Cookie(name="c6")
c7: str = rx.Cookie("c7 default")
# local storage with default settings
l1: str = rx.LocalStorage()
l2: rx.LocalStorage = "l2 default" # type: ignore
l2: str = rx.LocalStorage("l2 default")
# local storage with custom settings
l3: str = rx.LocalStorage(name="l3")
@ -56,7 +56,7 @@ def ClientSide():
# Session storage
s1: str = rx.SessionStorage()
s2: rx.SessionStorage = "s2 default" # type: ignore
s2: str = rx.SessionStorage("s2 default")
s3: str = rx.SessionStorage(name="s3")
def set_l6(self, my_param: str):
@ -87,13 +87,13 @@ def ClientSide():
rx.input(
placeholder="state var",
value=ClientSideState.state_var,
on_change=ClientSideState.set_state_var, # type: ignore
on_change=ClientSideState.setvar("state_var"),
id="state_var",
),
rx.input(
placeholder="input value",
value=ClientSideState.input_value,
on_change=ClientSideState.set_input_value, # type: ignore
on_change=ClientSideState.setvar("input_value"),
id="input_value",
),
rx.button(
@ -127,7 +127,7 @@ def ClientSide():
rx.box(ClientSideSubSubState.s1s, id="s1s"),
)
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
app.add_page(index)
app.add_page(index, route="/foo")
@ -321,6 +321,7 @@ async def test_client_side_state(
assert not driver.get_cookies()
local_storage_items = local_storage.items()
local_storage_items.pop("last_compiled_time", None)
local_storage_items.pop("theme", None)
assert not local_storage_items
# set some cookies and local storage values
@ -436,6 +437,7 @@ async def test_client_side_state(
local_storage_items = local_storage.items()
local_storage_items.pop("last_compiled_time", None)
local_storage_items.pop("theme", None)
assert local_storage_items.pop(f"{sub_state_name}.l1") == "l1 value"
assert local_storage_items.pop(f"{sub_state_name}.l2") == "l2 value"
assert local_storage_items.pop("l3") == "l3 value"

View File

@ -72,7 +72,7 @@ def ComponentStateApp():
State=_Counter,
)
app = rx.App(state=rx.State) # noqa
app = rx.App(_state=rx.State) # noqa
@rx.page()
def index():

View File

@ -36,7 +36,7 @@ def ConnectionBanner():
rx.button("Delay", id="delay", on_click=State.delay),
)
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
app.add_page(index)

View File

@ -26,7 +26,7 @@ def DeployUrlSample() -> None:
rx.button("GOTO SELF", on_click=State.goto_self, id="goto_self")
)
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
app.add_page(index)

View File

@ -138,7 +138,7 @@ def DynamicRoute():
def redirect_page():
return rx.fragment(rx.text("redirecting..."))
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load) # type: ignore
app.add_page(index, route="/static/x", on_load=DynamicState.on_load) # type: ignore
app.add_page(index)

View File

@ -156,7 +156,7 @@ def TestEventAction():
on_click=EventActionState.on_click("outer"), # type: ignore
)
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
app.add_page(index)

View File

@ -144,7 +144,7 @@ def EventChain():
time.sleep(0.5)
self.interim_value = "final"
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
token_input = rx.input(
value=State.router.session.client_token, is_read_only=True, id="token"

View File

@ -39,7 +39,7 @@ def TestApp():
"""
print(1 / number)
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
@app.add_page
def index():

View File

@ -30,7 +30,7 @@ def FormSubmit(form_component):
def form_submit(self, form_data: Dict):
self.form_data = form_data
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
@app.add_page
def index():
@ -90,7 +90,7 @@ def FormSubmitName(form_component):
def form_submit(self, form_data: Dict):
self.form_data = form_data
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
@app.add_page
def index():

View File

@ -16,7 +16,7 @@ def FullyControlledInput():
class State(rx.State):
text: str = "initial"
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
@app.add_page
def index():

View File

@ -45,7 +45,7 @@ def LoginSample():
rx.button("Do it", on_click=State.login, id="doit"),
)
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
app.add_page(index)
app.add_page(login)

View File

@ -38,7 +38,7 @@ def ServerSideEvent():
def set_value_return_c(self):
return rx.set_value("c", "")
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
@app.add_page
def index():

View File

@ -166,7 +166,7 @@ def UploadFile():
rx.text(UploadState.event_order.to_string(), id="event-order"),
)
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
app.add_page(index)

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