Skip to content

Commit 9b57ec1

Browse files
wip
1 parent 05fde77 commit 9b57ec1

5 files changed

Lines changed: 290 additions & 569 deletions

File tree

packages/reflex-base/src/reflex_base/event/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,26 @@ def __call__(self, *args: Any, **kwargs: Any) -> "EventSpec":
470470
)
471471

472472

473+
if TYPE_CHECKING:
474+
475+
def typing_event(fn: Callable[..., Any]) -> EventHandler:
476+
"""Mark ``fn`` so pyright sees it as the ``EventHandler`` that
477+
``BaseState.__init_subclass__`` will rewrite it into. No-op at runtime.
478+
479+
Args:
480+
fn: The method to mark.
481+
482+
Returns:
483+
``fn`` typed as :class:`EventHandler`.
484+
"""
485+
...
486+
487+
else:
488+
489+
def typing_event(fn: Callable[..., Any]) -> Callable[..., Any]: # noqa: D103
490+
return fn
491+
492+
473493
@dataclasses.dataclass(
474494
init=True,
475495
frozen=True,
@@ -2833,6 +2853,7 @@ def BaseState(self) -> "type[BaseState]": # noqa: N802
28332853

28342854
event = EventNamespace
28352855
event.event = event # pyright: ignore[reportAttributeAccessIssue]
2856+
event.typing_event = staticmethod(typing_event) # pyright: ignore[reportAttributeAccessIssue]
28362857
_this = sys.modules[__name__]
28372858
event.__path__ = _this.__path__ # pyright: ignore[reportAttributeAccessIssue]
28382859
event.__spec__ = _this.__spec__ # pyright: ignore[reportAttributeAccessIssue]

reflex/minify.py

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -108,18 +108,8 @@ def get_minify_config() -> MinifyConfig | None:
108108
return _load_minify_config_uncached()
109109

110110

111-
def is_minify_enabled() -> bool:
112-
"""Whether either state or event minification is enabled.
113-
114-
Returns:
115-
``True`` if either ``REFLEX_MINIFY_STATES`` or ``REFLEX_MINIFY_EVENTS``
116-
is on and a config exists.
117-
"""
118-
return is_state_minify_enabled() or is_event_minify_enabled()
119-
120-
121111
@functools.cache
122-
def _is_mode_enabled(env_var_name: str) -> bool:
112+
def is_mode_enabled(env_var_name: str) -> bool:
123113
"""Whether the given ``REFLEX_MINIFY_*`` env var is on and a config exists.
124114
125115
Args:
@@ -135,22 +125,16 @@ def _is_mode_enabled(env_var_name: str) -> bool:
135125
return env_var.get() == MinifyMode.ENABLED and get_minify_config() is not None
136126

137127

138-
def is_state_minify_enabled() -> bool:
139-
"""Whether state-id minification is enabled.
140-
141-
Returns:
142-
``True`` if ``REFLEX_MINIFY_STATES=enabled`` and ``minify.json`` exists.
143-
"""
144-
return _is_mode_enabled("REFLEX_MINIFY_STATES")
145-
146-
147-
def is_event_minify_enabled() -> bool:
148-
"""Whether event-id minification is enabled.
128+
def is_minify_enabled() -> bool:
129+
"""Whether either state or event minification is enabled.
149130
150131
Returns:
151-
``True`` if ``REFLEX_MINIFY_EVENTS=enabled`` and ``minify.json`` exists.
132+
``True`` when ``REFLEX_MINIFY_STATES`` or ``REFLEX_MINIFY_EVENTS`` is
133+
on and ``minify.json`` exists.
152134
"""
153-
return _is_mode_enabled("REFLEX_MINIFY_EVENTS")
135+
return is_mode_enabled("REFLEX_MINIFY_STATES") or is_mode_enabled(
136+
"REFLEX_MINIFY_EVENTS"
137+
)
154138

155139

156140
def save_minify_config(config: MinifyConfig) -> None:
@@ -316,7 +300,7 @@ def clear_config_cache() -> None:
316300
``REFLEX_MINIFY_*`` env vars at runtime.
317301
"""
318302
get_minify_config.cache_clear()
319-
_is_mode_enabled.cache_clear()
303+
is_mode_enabled.cache_clear()
320304
install_minify_resolver()
321305

322306

reflex/state.py

Lines changed: 79 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
EventHandler,
3737
EventSpec,
3838
call_script,
39+
typing_event,
3940
)
4041
from reflex_base.utils.exceptions import (
4142
ComputedVarShadowsBaseVarsError,
@@ -2268,12 +2269,6 @@ def wrapper() -> Component:
22682269
class FrontendEventExceptionState(State):
22692270
"""Substate for handling frontend exceptions."""
22702271

2271-
# ``__init_subclass__`` replaces the method below with an ``EventHandler``
2272-
# instance on the class. Splitting via ``TYPE_CHECKING`` lets callers
2273-
# (e.g. ``format_event_handler``) see the post-rewrite type.
2274-
if TYPE_CHECKING:
2275-
handle_frontend_exception: EventHandler
2276-
22772272
# If the frontend error message contains any of these strings, automatically reload the page.
22782273
auto_reload_on_errors: ClassVar[list[re.Pattern]] = [
22792274
re.compile( # Chrome/Edge
@@ -2291,68 +2286,62 @@ class FrontendEventExceptionState(State):
22912286
),
22922287
]
22932288

2294-
if not TYPE_CHECKING:
2289+
@typing_event
2290+
def handle_frontend_exception(
2291+
self, info: str, component_stack: str
2292+
) -> Iterator[EventSpec]:
2293+
"""Handle frontend exceptions.
22952294
2296-
@event
2297-
def handle_frontend_exception(
2298-
self, info: str, component_stack: str
2299-
) -> Iterator[EventSpec]:
2300-
"""Handle frontend exceptions.
2301-
2302-
If a frontend exception handler is provided, it will be called.
2303-
Otherwise, the default frontend exception handler will be called.
2304-
2305-
Args:
2306-
info: The exception information.
2307-
component_stack: The stack trace of the component where the exception occurred.
2308-
2309-
Yields:
2310-
Optional auto-reload event for certain errors outside cooldown period.
2311-
"""
2312-
# Handle automatic reload for certain errors.
2313-
if type(self).auto_reload_on_errors and any(
2314-
error.search(info) for error in type(self).auto_reload_on_errors
2315-
):
2316-
yield call_script(
2317-
f"const last_reload = parseInt(window.sessionStorage.getItem('{LAST_RELOADED_KEY}')) || 0;"
2318-
f"if (Date.now() - last_reload > {environment.REFLEX_AUTO_RELOAD_COOLDOWN_TIME_MS.get()})"
2319-
"{"
2320-
f"window.sessionStorage.setItem('{LAST_RELOADED_KEY}', Date.now().toString());"
2321-
"window.location.reload();"
2322-
"}"
2323-
)
2324-
prerequisites.get_and_validate_app().app.frontend_exception_handler(
2325-
Exception(info)
2295+
If a frontend exception handler is provided, it will be called.
2296+
Otherwise, the default frontend exception handler will be called.
2297+
2298+
Args:
2299+
info: The exception information.
2300+
component_stack: The stack trace of the component where the exception occurred.
2301+
2302+
Yields:
2303+
Optional auto-reload event for certain errors outside cooldown period.
2304+
"""
2305+
# Handle automatic reload for certain errors.
2306+
if type(self).auto_reload_on_errors and any(
2307+
error.search(info) for error in type(self).auto_reload_on_errors
2308+
):
2309+
yield call_script(
2310+
f"const last_reload = parseInt(window.sessionStorage.getItem('{LAST_RELOADED_KEY}')) || 0;"
2311+
f"if (Date.now() - last_reload > {environment.REFLEX_AUTO_RELOAD_COOLDOWN_TIME_MS.get()})"
2312+
"{"
2313+
f"window.sessionStorage.setItem('{LAST_RELOADED_KEY}', Date.now().toString());"
2314+
"window.location.reload();"
2315+
"}"
23262316
)
2317+
prerequisites.get_and_validate_app().app.frontend_exception_handler(
2318+
Exception(info)
2319+
)
23272320

23282321

23292322
class UpdateVarsInternalState(State):
23302323
"""Substate for handling internal state var updates."""
23312324

2332-
# See ``FrontendEventExceptionState`` for why this is split.
2333-
if TYPE_CHECKING:
2334-
update_vars_internal: EventHandler
2335-
else:
2325+
@typing_event
2326+
async def update_vars_internal(self, vars: dict[str, Any]) -> None:
2327+
"""Apply updates to fully qualified state vars.
23362328
2337-
async def update_vars_internal(self, vars: dict[str, Any]) -> None:
2338-
"""Apply updates to fully qualified state vars.
2329+
The keys in `vars` should be in the form of `{state.get_full_name()}.{var_name}`,
2330+
and each value will be set on the appropriate substate instance.
23392331
2340-
The keys in `vars` should be in the form of `{state.get_full_name()}.{var_name}`,
2341-
and each value will be set on the appropriate substate instance.
2332+
This function is primarily used to apply cookie and local storage
2333+
updates from the frontend to the appropriate substate.
23422334
2343-
This function is primarily used to apply cookie and local storage
2344-
updates from the frontend to the appropriate substate.
2345-
2346-
Args:
2347-
vars: The fully qualified vars and values to update.
2348-
"""
2349-
for var, value in vars.items():
2350-
state_name, _, var_name = var.rpartition(".")
2351-
var_name = var_name.removesuffix(FIELD_MARKER)
2352-
var_state_cls = State.get_class_substate(state_name)
2353-
if var_state_cls._is_client_storage(var_name):
2354-
var_state = await self.get_state(var_state_cls)
2355-
setattr(var_state, var_name, value)
2335+
Args:
2336+
vars: The fully qualified vars and values to update.
2337+
"""
2338+
for var, value in vars.items():
2339+
state_name, _, var_name = var.rpartition(".")
2340+
var_name = var_name.removesuffix(FIELD_MARKER)
2341+
var_state_cls = State.get_class_substate(state_name)
2342+
if var_state_cls._is_client_storage(var_name):
2343+
var_state = await self.get_state(var_state_cls)
2344+
setattr(var_state, var_name, value)
23562345

23572346

23582347
class OnLoadInternalState(State):
@@ -2361,47 +2350,42 @@ class OnLoadInternalState(State):
23612350
This is a separate substate to avoid deserializing the entire state tree for every page navigation.
23622351
"""
23632352

2364-
# See ``FrontendEventExceptionState`` for why this is split.
2365-
if TYPE_CHECKING:
2366-
on_load_internal: EventHandler
2367-
23682353
# Cannot properly annotate this as `App` due to circular import issues.
23692354
_app_ref: ClassVar[Any] = None
23702355

2371-
if not TYPE_CHECKING:
2356+
@typing_event
2357+
def on_load_internal(self) -> list[Event | EventSpec | event.EventCallback] | None:
2358+
"""Queue on_load handlers for the current page.
2359+
2360+
Returns:
2361+
The list of events to queue for on load handling.
2362+
2363+
Raises:
2364+
TypeError: If the app reference is not of type App.
2365+
"""
2366+
from reflex.app import App
23722367

2373-
def on_load_internal(
2374-
self,
2375-
) -> list[Event | EventSpec | event.EventCallback] | None:
2376-
"""Queue on_load handlers for the current page.
2377-
2378-
Returns:
2379-
The list of events to queue for on load handling.
2380-
2381-
Raises:
2382-
TypeError: If the app reference is not of type App.
2383-
"""
2384-
from reflex.app import App
2385-
2386-
app = type(self)._app_ref or prerequisites.get_and_validate_app().app
2387-
if not isinstance(app, App):
2388-
msg = f"Expected app to be of type {App.__name__}, got {type(app).__name__}."
2389-
raise TypeError(msg)
2390-
# Cache the app reference for subsequent calls.
2391-
if type(self)._app_ref is None:
2392-
type(self)._app_ref = app
2393-
load_events = app.get_load_events(self.router.url.path)
2394-
if not load_events:
2395-
self.is_hydrated = True
2396-
return None # Fast path for navigation with no on_load events defined.
2397-
self.is_hydrated = False
2398-
return [
2399-
*Event.from_event_type(
2400-
load_events,
2401-
router_data=self.router_data,
2402-
),
2403-
State.set_is_hydrated(True),
2404-
]
2368+
app = type(self)._app_ref or prerequisites.get_and_validate_app().app
2369+
if not isinstance(app, App):
2370+
msg = (
2371+
f"Expected app to be of type {App.__name__}, got {type(app).__name__}."
2372+
)
2373+
raise TypeError(msg)
2374+
# Cache the app reference for subsequent calls.
2375+
if type(self)._app_ref is None:
2376+
type(self)._app_ref = app
2377+
load_events = app.get_load_events(self.router.url.path)
2378+
if not load_events:
2379+
self.is_hydrated = True
2380+
return None # Fast path for navigation with no on_load events defined.
2381+
self.is_hydrated = False
2382+
return [
2383+
*Event.from_event_type(
2384+
load_events,
2385+
router_data=self.router_data,
2386+
),
2387+
State.set_is_hydrated(True),
2388+
]
24052389

24062390

24072391
class ComponentState(State, mixin=True):

0 commit comments

Comments
 (0)