Merge branch 'main' into add-validation-to-function-vars
This commit is contained in:
commit
24f341d125
18
.github/workflows/benchmarks.yml
vendored
18
.github/workflows/benchmarks.yml
vendored
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.")
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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
2
poetry.lock
generated
@ -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"},
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
|
153
reflex/app.py
153
reflex/app.py
@ -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__"):
|
||||
|
@ -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."
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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 = ""
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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))
|
||||
|
||||
|
||||
|
@ -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]):
|
||||
|
@ -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.
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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)",
|
||||
},
|
||||
|
@ -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]
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
|
@ -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"]
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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}"
|
||||
|
@ -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):
|
||||
|
@ -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",
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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]]:
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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):
|
||||
|
@ -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.")
|
||||
|
@ -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:
|
||||
|
@ -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."""
|
||||
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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]
|
||||
|
@ -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):
|
||||
|
@ -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}: ...
|
||||
"""
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"]
|
||||
)
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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():
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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():
|
||||
|
@ -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():
|
||||
|
@ -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():
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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():
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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():
|
||||
|
@ -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():
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
"""
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user