Merge branch 'main' into add-validation-to-function-vars

This commit is contained in:
Khaleel Al-Adhami 2025-01-23 10:26:04 -08:00
commit 24f341d125
79 changed files with 765 additions and 397 deletions

View File

@ -81,18 +81,22 @@ jobs:
matrix:
# Show OS combos first in GUI
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.10.16', '3.11.11', '3.12.8']
python-version: ["3.10.16", "3.11.11", "3.12.8"]
exclude:
- os: windows-latest
python-version: '3.10.16'
python-version: "3.10.16"
- os: windows-latest
python-version: "3.11.11"
# keep only one python version for MacOS
- os: macos-latest
python-version: '3.10.16'
python-version: "3.10.16"
- os: macos-latest
python-version: "3.11.11"
include:
- os: windows-latest
python-version: '3.10.11'
python-version: "3.10.11"
- os: windows-latest
python-version: "3.11.9"
runs-on: ${{ matrix.os }}
steps:
@ -155,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

@ -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

@ -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)

2
poetry.lock generated
View File

@ -461,7 +461,6 @@ files = [
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"},
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"},
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"},
{file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"},
@ -472,7 +471,6 @@ files = [
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"},
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"},
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"},
{file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"},

View File

@ -87,15 +87,18 @@ reportIncompatibleVariableOverride = false
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]

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

@ -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,13 @@ 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:
self.api.mount(str(constants.Endpoint.EVENT), socket_app)
# Check the exception handlers
self._validate_exception_handlers()
@ -397,24 +416,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 +462,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 +553,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 +572,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 +595,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 +607,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 +621,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 +645,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 +703,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 +810,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 +825,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 +858,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 +888,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 +912,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 +939,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 +958,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 +973,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 +1015,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 +1028,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 +1063,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 +1185,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 +1197,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 +1209,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

@ -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

@ -190,7 +190,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

@ -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"

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

@ -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

@ -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

@ -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

@ -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

@ -536,10 +536,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]]:

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

@ -573,6 +573,20 @@ def deploy(
)
@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():
@ -2353,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)
@ -2366,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 +2877,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 +2900,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}"
)
@ -4057,10 +4056,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:
@ -4074,10 +4073,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

@ -85,7 +85,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,9 +296,9 @@ class AppHarness:
raise RuntimeError("App was not initialized.")
if isinstance(self.app_instance._state_manager, StateManagerRedis):
# Create our own redis connection for testing.
if self.app_instance.state is None:
if self.app_instance._state is None:
raise RuntimeError("App state is not initialized.")
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
@ -326,7 +326,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(
@ -355,12 +355,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.")

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

@ -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."""
@ -143,35 +143,35 @@ 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."""
@ -187,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."""

View File

@ -364,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",
@ -377,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",

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,7 +38,7 @@ 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,
GeneratedCodeHasNoFunctionDefsError,
SystemPackageMissingError,
)
from reflex.utils.format import format_library_name
@ -463,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.
@ -921,6 +1083,7 @@ def install_bun():
constants.Bun.INSTALL_URL,
f"bun-v{constants.Bun.VERSION}",
BUN_INSTALL=str(constants.Bun.ROOT_PATH),
BUN_VERSION=str(constants.Bun.VERSION),
)
@ -1653,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.
@ -1670,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

@ -1909,7 +1909,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):

View File

@ -1802,13 +1802,13 @@ def _generate_overloads_for_function_var_call(maximum_args: int = 4) -> str:
)
]
function_type_hint = f"""FunctionVar[ReflexCallable[[{", ".join(required_params + optional_params)}], {return_type}]]"""
NEWLINE = "\n"
new_line = "\n"
overloads.append(
f"""
@overload
def call(
self: {function_type_hint},
{"," + NEWLINE + " ".join(required_args + optional_args)}
{"," + new_line + " ".join(required_args + optional_args)}
) -> {return_type_var}: ...
"""
)

View File

@ -22,7 +22,7 @@ from typing import (
from typing_extensions import Unpack
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 (
@ -915,10 +915,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

@ -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

@ -188,7 +188,7 @@ 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

View File

@ -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

@ -158,7 +158,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)

View File

@ -41,7 +41,7 @@ def VarOperations():
dict2: rx.Field[Dict[int, int]] = rx.field({3: 4})
html_str: rx.Field[str] = rx.field("<div>hello</div>")
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
@rx.memo
def memo_comp(list1: List[int], int_var1: int, id: str):

View File

@ -16,7 +16,7 @@ def DatetimeOperationsApp():
date2: datetime = datetime(2031, 1, 1)
date3: datetime = datetime(2021, 1, 1)
app = rx.App(state=DtOperationsState)
app = rx.App(_state=DtOperationsState)
@app.add_page
def index():

View File

@ -20,7 +20,7 @@ def Table():
"""App using table component."""
import reflex as rx
app = rx.App(state=rx.State)
app = rx.App(_state=rx.State)
@app.add_page
def index():

View File

@ -1,11 +1,8 @@
"""Test fixtures."""
import asyncio
import contextlib
import os
import platform
import uuid
from pathlib import Path
from typing import Dict, Generator, Type
from unittest import mock
@ -14,6 +11,7 @@ import pytest
from reflex.app import App
from reflex.event import EventSpec
from reflex.model import ModelRegistry
from reflex.testing import chdir
from reflex.utils import prerequisites
from .states import (
@ -191,33 +189,6 @@ def router_data(router_data_headers) -> Dict[str, str]:
}
# borrowed from py3.11
class chdir(contextlib.AbstractContextManager):
"""Non thread-safe context manager to change the current working directory."""
def __init__(self, path):
"""Prepare contextmanager.
Args:
path: the path to change to
"""
self.path = path
self._old_cwd = []
def __enter__(self):
"""Save current directory and perform chdir."""
self._old_cwd.append(Path.cwd())
os.chdir(self.path)
def __exit__(self, *excinfo):
"""Change back to previous directory on stack.
Args:
excinfo: sys.exc_info captured in the context block
"""
os.chdir(self._old_cwd.pop())
@pytest.fixture
def tmp_working_dir(tmp_path):
"""Create a temporary directory and chdir to it.

View File

@ -41,7 +41,7 @@ async def test_preprocess_no_events(hydrate_middleware, event1, mocker):
mocker.patch("reflex.state.State.class_subclasses", {TestState})
state = State()
update = await hydrate_middleware.preprocess(
app=App(state=State),
app=App(_state=State),
event=event1,
state=state,
)

View File

@ -235,14 +235,14 @@ def test_add_page_default_route(app: App, index_page, about_page):
index_page: The index page.
about_page: The about page.
"""
assert app.pages == {}
assert app.unevaluated_pages == {}
assert app._pages == {}
assert app._unevaluated_pages == {}
app.add_page(index_page)
app._compile_page("index")
assert app.pages.keys() == {"index"}
assert app._pages.keys() == {"index"}
app.add_page(about_page)
app._compile_page("about")
assert app.pages.keys() == {"index", "about"}
assert app._pages.keys() == {"index", "about"}
def test_add_page_set_route(app: App, index_page, windows_platform: bool):
@ -254,10 +254,10 @@ def test_add_page_set_route(app: App, index_page, windows_platform: bool):
windows_platform: Whether the system is windows.
"""
route = "test" if windows_platform else "/test"
assert app.unevaluated_pages == {}
assert app._unevaluated_pages == {}
app.add_page(index_page, route=route)
app._compile_page("test")
assert app.pages.keys() == {"test"}
assert app._pages.keys() == {"test"}
def test_add_page_set_route_dynamic(index_page, windows_platform: bool):
@ -267,18 +267,18 @@ def test_add_page_set_route_dynamic(index_page, windows_platform: bool):
index_page: The index page.
windows_platform: Whether the system is windows.
"""
app = App(state=EmptyState)
assert app.state is not None
app = App(_state=EmptyState)
assert app._state is not None
route = "/test/[dynamic]"
assert app.unevaluated_pages == {}
assert app._unevaluated_pages == {}
app.add_page(index_page, route=route)
app._compile_page("test/[dynamic]")
assert app.pages.keys() == {"test/[dynamic]"}
assert "dynamic" in app.state.computed_vars
assert app.state.computed_vars["dynamic"]._deps(objclass=EmptyState) == {
assert app._pages.keys() == {"test/[dynamic]"}
assert "dynamic" in app._state.computed_vars
assert app._state.computed_vars["dynamic"]._deps(objclass=EmptyState) == {
constants.ROUTER
}
assert constants.ROUTER in app.state()._computed_var_dependencies
assert constants.ROUTER in app._state()._computed_var_dependencies
def test_add_page_set_route_nested(app: App, index_page, windows_platform: bool):
@ -290,9 +290,9 @@ def test_add_page_set_route_nested(app: App, index_page, windows_platform: bool)
windows_platform: Whether the system is windows.
"""
route = "test\\nested" if windows_platform else "/test/nested"
assert app.unevaluated_pages == {}
assert app._unevaluated_pages == {}
app.add_page(index_page, route=route)
assert app.unevaluated_pages.keys() == {route.strip(os.path.sep)}
assert app._unevaluated_pages.keys() == {route.strip(os.path.sep)}
def test_add_page_invalid_api_route(app: App, index_page):
@ -412,8 +412,8 @@ async def test_initialize_with_state(test_state: Type[ATestState], token: str):
test_state: The default state.
token: a Token.
"""
app = App(state=test_state)
assert app.state == test_state
app = App(_state=test_state)
assert app._state == test_state
# Get a state for a given token.
state = await app.state_manager.get_state(_substate_key(token, test_state))
@ -431,7 +431,7 @@ async def test_set_and_get_state(test_state):
Args:
test_state: The default state.
"""
app = App(state=test_state)
app = App(_state=test_state)
# Create two tokens.
token1 = str(uuid.uuid4()) + f"_{test_state.get_full_name()}"
@ -826,7 +826,7 @@ async def test_upload_file_without_annotation(state, tmp_path, token):
token: a Token.
"""
state._tmp_path = tmp_path
app = App(state=State)
app = App(_state=State)
request_mock = unittest.mock.Mock()
request_mock.headers = {
@ -860,7 +860,7 @@ async def test_upload_file_background(state, tmp_path, token):
token: a Token.
"""
state._tmp_path = tmp_path
app = App(state=State)
app = App(_state=State)
request_mock = unittest.mock.Mock()
request_mock.headers = {
@ -937,8 +937,8 @@ def test_dynamic_arg_shadow(
"""
arg_name = "counter"
route = f"/test/[{arg_name}]"
app = app_module_mock.app = App(state=DynamicState)
assert app.state is not None
app = app_module_mock.app = App(_state=DynamicState)
assert app._state is not None
with pytest.raises(NameError):
app.add_page(index_page, route=route, on_load=DynamicState.on_load) # type: ignore
@ -962,7 +962,7 @@ def test_multiple_dynamic_args(
arg_name = "my_arg"
route = f"/test/[{arg_name}]"
route2 = f"/test2/[{arg_name}]"
app = app_module_mock.app = App(state=EmptyState)
app = app_module_mock.app = App(_state=EmptyState)
app.add_page(index_page, route=route)
app.add_page(index_page, route=route2)
@ -989,16 +989,16 @@ async def test_dynamic_route_var_route_change_completed_on_load(
"""
arg_name = "dynamic"
route = f"/test/[{arg_name}]"
app = app_module_mock.app = App(state=DynamicState)
assert app.state is not None
assert arg_name not in app.state.vars
app = app_module_mock.app = App(_state=DynamicState)
assert app._state is not None
assert arg_name not in app._state.vars
app.add_page(index_page, route=route, on_load=DynamicState.on_load) # type: ignore
assert arg_name in app.state.vars
assert arg_name in app.state.computed_vars
assert app.state.computed_vars[arg_name]._deps(objclass=DynamicState) == {
assert arg_name in app._state.vars
assert arg_name in app._state.computed_vars
assert app._state.computed_vars[arg_name]._deps(objclass=DynamicState) == {
constants.ROUTER
}
assert constants.ROUTER in app.state()._computed_var_dependencies
assert constants.ROUTER in app._state()._computed_var_dependencies
substate_token = _substate_key(token, DynamicState)
sid = "mock_sid"
@ -1173,7 +1173,7 @@ async def test_process_events(mocker, token: str):
"headers": {},
"ip": "127.0.0.1",
}
app = App(state=GenState)
app = App(_state=GenState)
mocker.patch.object(app, "_postprocess", AsyncMock())
event = Event(
@ -1219,7 +1219,7 @@ def test_overlay_component(
overlay_component: The overlay_component to pass to App.
exp_page_child: The type of the expected child in the page fragment.
"""
app = App(state=state, overlay_component=overlay_component)
app = App(_state=state, overlay_component=overlay_component)
app._setup_overlay_component()
if exp_page_child is None:
assert app.overlay_component is None
@ -1238,7 +1238,7 @@ def test_overlay_component(
# overlay components are wrapped during compile only
app._compile_page("test")
app._setup_overlay_component()
page = app.pages["test"]
page = app._pages["test"]
if exp_page_child is not None:
assert len(page.children) == 3
@ -1356,52 +1356,52 @@ def test_app_wrap_priority(compilable_app: tuple[App, Path]):
def test_app_state_determination():
"""Test that the stateless status of an app is determined correctly."""
a1 = App()
assert a1.state is None
assert a1._state is None
# No state, no router, no event handlers.
a1.add_page(rx.box("Index"), route="/")
assert a1.state is None
assert a1._state is None
# Add a page with `on_load` enables state.
a1.add_page(rx.box("About"), route="/about", on_load=rx.console_log(""))
a1._compile_page("about")
assert a1.state is not None
assert a1._state is not None
a2 = App()
assert a2.state is None
assert a2._state is None
# Referencing a state Var enables state.
a2.add_page(rx.box(rx.text(GenState.value)), route="/")
a2._compile_page("index")
assert a2.state is not None
assert a2._state is not None
a3 = App()
assert a3.state is None
assert a3._state is None
# Referencing router enables state.
a3.add_page(rx.box(rx.text(State.router.page.full_path)), route="/")
a3._compile_page("index")
assert a3.state is not None
assert a3._state is not None
a4 = App()
assert a4.state is None
assert a4._state is None
a4.add_page(rx.box(rx.button("Click", on_click=rx.console_log(""))), route="/")
assert a4.state is None
assert a4._state is None
a4.add_page(
rx.box(rx.button("Click", on_click=DynamicState.on_counter)), route="/page2"
)
a4._compile_page("page2")
assert a4.state is not None
assert a4._state is not None
def test_raise_on_state():
"""Test that the state is set."""
# state kwargs is deprecated, we just make sure the app is created anyway.
_app = App(state=State)
assert _app.state is not None
assert issubclass(_app.state, State)
_app = App(_state=State)
assert _app._state is not None
assert issubclass(_app._state, State)
def test_call_app():
@ -1468,7 +1468,7 @@ def test_add_page_component_returning_tuple():
app._compile_page("index")
app._compile_page("page2")
fragment_wrapper = app.pages["index"].children[0]
fragment_wrapper = app._pages["index"].children[0]
assert isinstance(fragment_wrapper, Fragment)
first_text = fragment_wrapper.children[0]
assert isinstance(first_text, Text)
@ -1478,7 +1478,7 @@ def test_add_page_component_returning_tuple():
assert str(second_text.children[0].contents) == '"second"' # type: ignore
# Test page with trailing comma.
page2_fragment_wrapper = app.pages["page2"].children[0]
page2_fragment_wrapper = app._pages["page2"].children[0]
assert isinstance(page2_fragment_wrapper, Fragment)
third_text = page2_fragment_wrapper.children[0]
assert isinstance(third_text, Text)
@ -1552,7 +1552,7 @@ def test_app_with_valid_var_dependencies(compilable_app: tuple[App, Path]):
def bar(self) -> str:
return "bar"
app.state = ValidDepState
app._state = ValidDepState
app._compile()
@ -1564,7 +1564,7 @@ def test_app_with_invalid_var_dependencies(compilable_app: tuple[App, Path]):
def bar(self) -> str:
return "bar"
app.state = InvalidDepState
app._state = InvalidDepState
with pytest.raises(exceptions.VarDependencyError):
app._compile()

View File

@ -1,20 +1,28 @@
import json
import re
import shutil
import tempfile
from pathlib import Path
from unittest.mock import Mock, mock_open
import pytest
from typer.testing import CliRunner
from reflex import constants
from reflex.config import Config
from reflex.reflex import cli
from reflex.testing import chdir
from reflex.utils.prerequisites import (
CpuInfo,
_update_next_config,
cached_procedure,
get_cpu_info,
initialize_requirements_txt,
rename_imports_and_app_name,
)
runner = CliRunner()
@pytest.mark.parametrize(
"config, export, expected_output",
@ -224,3 +232,156 @@ def test_get_cpu_info():
for attr in ("manufacturer_id", "model_name", "address_width"):
value = getattr(cpu_info, attr)
assert value.strip() if attr != "address_width" else value
@pytest.fixture
def temp_directory():
temp_dir = tempfile.mkdtemp()
yield Path(temp_dir)
shutil.rmtree(temp_dir)
@pytest.mark.parametrize(
"config_code,expected",
[
("rx.Config(app_name='old_name')", 'rx.Config(app_name="new_name")'),
('rx.Config(app_name="old_name")', 'rx.Config(app_name="new_name")'),
("rx.Config('old_name')", 'rx.Config("new_name")'),
('rx.Config("old_name")', 'rx.Config("new_name")'),
],
)
def test_rename_imports_and_app_name(temp_directory, config_code, expected):
file_path = temp_directory / "rxconfig.py"
content = f"""
config = {config_code}
"""
file_path.write_text(content)
rename_imports_and_app_name(file_path, "old_name", "new_name")
updated_content = file_path.read_text()
expected_content = f"""
config = {expected}
"""
assert updated_content == expected_content
def test_regex_edge_cases(temp_directory):
file_path = temp_directory / "example.py"
content = """
from old_name.module import something
import old_name
from old_name import something_else as alias
from old_name
"""
file_path.write_text(content)
rename_imports_and_app_name(file_path, "old_name", "new_name")
updated_content = file_path.read_text()
expected_content = """
from new_name.module import something
import new_name
from new_name import something_else as alias
from new_name
"""
assert updated_content == expected_content
def test_cli_rename_command(temp_directory):
foo_dir = temp_directory / "foo"
foo_dir.mkdir()
(foo_dir / "__init__").touch()
(foo_dir / ".web").mkdir()
(foo_dir / "assets").mkdir()
(foo_dir / "foo").mkdir()
(foo_dir / "foo" / "__init__.py").touch()
(foo_dir / "rxconfig.py").touch()
(foo_dir / "rxconfig.py").write_text(
"""
import reflex as rx
config = rx.Config(
app_name="foo",
)
"""
)
(foo_dir / "foo" / "components").mkdir()
(foo_dir / "foo" / "components" / "__init__.py").touch()
(foo_dir / "foo" / "components" / "base.py").touch()
(foo_dir / "foo" / "components" / "views.py").touch()
(foo_dir / "foo" / "components" / "base.py").write_text(
"""
import reflex as rx
from foo.components import views
from foo.components.views import *
from .base import *
def random_component():
return rx.fragment()
"""
)
(foo_dir / "foo" / "foo.py").touch()
(foo_dir / "foo" / "foo.py").write_text(
"""
import reflex as rx
import foo.components.base
from foo.components.base import random_component
class State(rx.State):
pass
def index():
return rx.text("Hello, World!")
app = rx.App()
app.add_page(index)
"""
)
with chdir(temp_directory / "foo"):
result = runner.invoke(cli, ["rename", "bar"])
assert result.exit_code == 0
assert (foo_dir / "rxconfig.py").read_text() == (
"""
import reflex as rx
config = rx.Config(
app_name="bar",
)
"""
)
assert (foo_dir / "bar").exists()
assert not (foo_dir / "foo").exists()
assert (foo_dir / "bar" / "components" / "base.py").read_text() == (
"""
import reflex as rx
from bar.components import views
from bar.components.views import *
from .base import *
def random_component():
return rx.fragment()
"""
)
assert (foo_dir / "bar" / "bar.py").exists()
assert not (foo_dir / "bar" / "foo.py").exists()
assert (foo_dir / "bar" / "bar.py").read_text() == (
"""
import reflex as rx
import bar.components.base
from bar.components.base import random_component
class State(rx.State):
pass
def index():
return rx.text("Hello, World!")
app = rx.App()
app.add_page(index)
"""
)

View File

@ -89,7 +89,7 @@ def app():
],
)
def test_check_routes_conflict_invalid(mocker, app, route1, route2):
mocker.patch.object(app, "pages", {route1: []})
mocker.patch.object(app, "_pages", {route1: []})
with pytest.raises(ValueError):
app._check_routes_conflict(route2)
@ -117,6 +117,6 @@ def test_check_routes_conflict_invalid(mocker, app, route1, route2):
],
)
def test_check_routes_conflict_valid(mocker, app, route1, route2):
mocker.patch.object(app, "pages", {route1: []})
mocker.patch.object(app, "_pages", {route1: []})
# test that running this does not throw an error.
app._check_routes_conflict(route2)

View File

@ -1912,12 +1912,12 @@ def mock_app_simple(monkeypatch) -> rx.App:
Returns:
The app, after mocking out prerequisites.get_app()
"""
app = App(state=TestState)
app = App(_state=TestState)
app_module = Mock()
setattr(app_module, CompileVars.APP, app)
app.state = TestState
app._state = TestState
app.event_namespace.emit = CopyingAsyncMock() # type: ignore
def _mock_get_app(*args, **kwargs):
@ -2161,7 +2161,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str):
token: A token.
"""
router_data = {"query": {}}
mock_app.state_manager.state = mock_app.state = BackgroundTaskState
mock_app.state_manager.state = mock_app._state = BackgroundTaskState
async for update in rx.app.process( # type: ignore
mock_app,
Event(
@ -2179,7 +2179,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str):
# wait for the coroutine to start
await asyncio.sleep(0.5 if CI else 0.1)
assert len(mock_app.background_tasks) == 1
assert len(mock_app._background_tasks) == 1
# Process another normal event
async for update in rx.app.process( # type: ignore
@ -2211,9 +2211,9 @@ async def test_background_task_no_block(mock_app: rx.App, token: str):
)
# Explicit wait for background tasks
for task in tuple(mock_app.background_tasks):
for task in tuple(mock_app._background_tasks):
await task
assert not mock_app.background_tasks
assert not mock_app._background_tasks
exp_order = [
"background_task:start",
@ -2292,7 +2292,7 @@ async def test_background_task_reset(mock_app: rx.App, token: str):
token: A token.
"""
router_data = {"query": {}}
mock_app.state_manager.state = mock_app.state = BackgroundTaskState
mock_app.state_manager.state = mock_app._state = BackgroundTaskState
async for update in rx.app.process( # type: ignore
mock_app,
Event(
@ -2309,9 +2309,9 @@ async def test_background_task_reset(mock_app: rx.App, token: str):
assert update == StateUpdate()
# Explicit wait for background tasks
for task in tuple(mock_app.background_tasks):
for task in tuple(mock_app._background_tasks):
await task
assert not mock_app.background_tasks
assert not mock_app._background_tasks
assert (
await mock_app.state_manager.get_state(
@ -2896,7 +2896,7 @@ async def test_preprocess(app_module_mock, token, test_state, expected, mocker):
"reflex.state.State.class_subclasses", {test_state, OnLoadInternalState}
)
app = app_module_mock.app = App(
state=State, load_events={"index": [test_state.test_handler]}
_state=State, _load_events={"index": [test_state.test_handler]}
)
async with app.state_manager.modify_state(_substate_key(token, State)) as state:
state.router_data = {"simulate": "hydrate"}
@ -2943,8 +2943,8 @@ async def test_preprocess_multiple_load_events(app_module_mock, token, mocker):
"reflex.state.State.class_subclasses", {OnLoadState, OnLoadInternalState}
)
app = app_module_mock.app = App(
state=State,
load_events={"index": [OnLoadState.test_handler, OnLoadState.test_handler]},
_state=State,
_load_events={"index": [OnLoadState.test_handler, OnLoadState.test_handler]},
)
async with app.state_manager.modify_state(_substate_key(token, State)) as state:
state.router_data = {"simulate": "hydrate"}
@ -2989,7 +2989,7 @@ async def test_get_state(mock_app: rx.App, token: str):
mock_app: An app that will be returned by `get_app()`
token: A token.
"""
mock_app.state_manager.state = mock_app.state = TestState
mock_app.state_manager.state = mock_app._state = TestState
# Get instance of ChildState2.
test_state = await mock_app.state_manager.get_state(
@ -3159,7 +3159,7 @@ async def test_get_state_from_sibling_not_cached(mock_app: rx.App, token: str):
pass
mock_app.state_manager.state = mock_app.state = Parent
mock_app.state_manager.state = mock_app._state = Parent
# Get the top level state via unconnected sibling.
root = await mock_app.state_manager.get_state(_substate_key(token, Child))

View File

@ -222,7 +222,7 @@ async def state_manager_redis(
Yields:
A state manager instance
"""
app_module_mock.app = rx.App(state=Root)
app_module_mock.app = rx.App(_state=Root)
state_manager = app_module_mock.app.state_manager
if not isinstance(state_manager, StateManagerRedis):

View File

@ -23,7 +23,7 @@ def test_app_harness(tmp_path):
class State(rx.State):
pass
app = rx.App(state=State)
app = rx.App(_state=State)
app.add_page(lambda: rx.text("Basic App"), route="/", title="index")
app._compile()

View File

@ -12,7 +12,7 @@ from reflex.base import Base
from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
from reflex.state import BaseState
from reflex.utils.exceptions import (
PrimitiveUnserializableToJSON,
PrimitiveUnserializableToJSONError,
UntypedComputedVarError,
)
from reflex.utils.imports import ImportVar
@ -1234,7 +1234,7 @@ def test_inf_and_nan(var, expected_js):
assert str(var) == expected_js
assert isinstance(var, NumberVar)
assert isinstance(var, LiteralVar)
with pytest.raises(PrimitiveUnserializableToJSON):
with pytest.raises(PrimitiveUnserializableToJSONError):
var.json()