Compare commits

...

16 Commits

Author SHA1 Message Date
Masen Furer
9308caa215
bump to v0.6.3.post1 2024-10-23 16:11:58 -07:00
Masen Furer
68de3f41c4
[ENG-3989] Ensure non-serialized states are present in StateManagerDisk (#4230)
If a non-root state was serialized, but its substates were not, then these
would not be populated when reloading the pickled state, because only substates
from the root were being populated with fresh versions.

Now, capture the substates from all fresh states and apply them to the
deserialized state for each substate to ensure that the entire state tree has
all substates instantiated after deserializing, even substates that were never
serialized originally.
2024-10-23 16:11:06 -07:00
Masen Furer
addf633692
pyproject.toml: bump to 0.6.3 2024-10-18 09:22:07 -07:00
Masen Furer
004518dd51
pyproject: bump to 0.6.3a4 2024-10-17 19:22:20 -07:00
Masen Furer
b112d35f62
Fix runtime error on python 3.11.0 (#4197)
All generic types present in a Union must be parametrized on 3.11.0 if any
other generic types in the union are parametrized.

This appears to be a bug in 3.11.0, as the behavior is not observed in 3.11.1
or 3.10; fixing here as this is technically more correct anyway and avoids a
crash.
2024-10-17 19:22:12 -07:00
Masen Furer
2aea1f29b6
pyproject: bump to 0.6.3a3 2024-10-17 16:58:16 -07:00
Masen Furer
0457cbd747
When REDIS_URL is set, use redis, regardless of config preference. (#4196)
We might change this down the road, but we don't want to introduce a breaking
change at this time.
2024-10-17 16:58:08 -07:00
Masen Furer
a6b2e640ac
[ENG-3954] Treat ArrayVar.foreach index as int (#4193)
* [ENG-3954] Treat ArrayVar.foreach index as int

* foreach: convert return value to a Var

When the value returned from the foreach is not hashable (mutable type), then
it will raise an exception if it is not first converted to a LiteralVar.
2024-10-17 16:58:07 -07:00
Masen Furer
542382c3e7
bump to 0.6.3a2 2024-10-16 15:15:08 -07:00
Khaleel Al-Adhami
20398c10f1
fix pyi for untyped event handlers (#4186)
* fix pyi for untyped event handlers

* no more empty lambdas
2024-10-16 15:14:58 -07:00
Masen Furer
f8881c391d
Arbitrary arg access two levels deep for untyped handler (#4180)
* Arbitrary arg access two levels deep for untyped handler

Provide drop-in compatibility with existing component wrapping code
that was accessing attributes on the default handler arg type.

* py3.9 compat
2024-10-16 15:14:58 -07:00
Masen Furer
c460040040
LiteralEventChainVar becomes an ArgsFunctionOperation (#4174)
* LiteralEventChainVar becomes an ArgsFunctionOperation

Instead of using the ArgsFunctionOperation to create the string representation
of the _js_expr, make the identity of the var an ArgsFunctionOperation so the
_args_names and _return_expr remain accessible.

Rely on the default behavior of ArgsFunctionOperation to create the
_cached_var_name / _js_expr value.

This allows the compat shim in `format_event_chain` to remain functional, as it
does special handling for ArgsFunctionOperation to retain the previous behavior
of that function (this was a regression introduced in 0.6.2).

* _var_type is EventChain; fix parent class order

* Re-fix LiteralEventChainVar inheritence list w/ comment

* [ENG-3942] LiteralEventVar becomes VarCallOperation

instead of using `.call` when constructing the `_js_expr`, have the identity of
a LiteralEventVar as a VarCallOperation to take advantage of the _var_data
carrying.

* add event overlords

* EventCallback descriptor always returns EventSpec from class

Relax actual `__get__` definition to support the multitude of overloads

* test case for event related vars carrying _var_data

---------

Co-authored-by: Khaleel Al-Adhami <khaleel.aladhami@gmail.com>
2024-10-16 15:14:58 -07:00
Khaleel Al-Adhami
19bfdc4035
disk is memory is disk (#4185) 2024-10-16 15:14:58 -07:00
Khaleel Al-Adhami
f133bf53cc
only treat dict object vars as key value mapping (#4177) 2024-10-16 15:14:58 -07:00
Masen Furer
11dcce3975
pin AppHarness tests to ubuntu-22.04 runner (#4173) 2024-10-16 15:14:57 -07:00
Masen Furer
3494a2d3f3
bump to 0.6.3a1 2024-10-14 08:57:37 -07:00
20 changed files with 282 additions and 168 deletions

View File

@ -14,7 +14,7 @@ env:
jobs: jobs:
check_latest_node: check_latest_node:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
strategy: strategy:
matrix: matrix:
python-version: ['3.12'] python-version: ['3.12']

View File

@ -24,7 +24,7 @@ jobs:
matrix: matrix:
state_manager: ['redis', 'memory'] state_manager: ['redis', 'memory']
python-version: ['3.11.5', '3.12.0'] python-version: ['3.11.5', '3.12.0']
runs-on: ubuntu-latest runs-on: ubuntu-22.04
services: services:
# Label used to access the service container # Label used to access the service container
redis: redis:

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "reflex" name = "reflex"
version = "0.6.3dev1" version = "0.6.3.post1"
description = "Web apps in pure Python." description = "Web apps in pure Python."
license = "Apache-2.0" license = "Apache-2.0"
authors = [ authors = [

View File

@ -31,7 +31,7 @@ class ErrorBoundary(Component):
on_click: Optional[EventType[[]]] = None, on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None, on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None, on_double_click: Optional[EventType[[]]] = None,
on_error: Optional[EventType[[]]] = None, on_error: Optional[EventType] = None,
on_focus: Optional[EventType[[]]] = None, on_focus: Optional[EventType[[]]] = None,
on_mount: Optional[EventType[[]]] = None, on_mount: Optional[EventType[[]]] = None,
on_mouse_down: Optional[EventType[[]]] = None, on_mouse_down: Optional[EventType[[]]] = None,

View File

@ -45,6 +45,7 @@ from reflex.event import (
EventVar, EventVar,
call_event_fn, call_event_fn,
call_event_handler, call_event_handler,
empty_event,
get_handler_args, get_handler_args,
) )
from reflex.style import Style, format_as_emotion from reflex.style import Style, format_as_emotion
@ -623,21 +624,21 @@ class Component(BaseComponent, ABC):
""" """
default_triggers = { default_triggers = {
EventTriggers.ON_FOCUS: lambda: [], EventTriggers.ON_FOCUS: empty_event,
EventTriggers.ON_BLUR: lambda: [], EventTriggers.ON_BLUR: empty_event,
EventTriggers.ON_CLICK: lambda: [], EventTriggers.ON_CLICK: empty_event,
EventTriggers.ON_CONTEXT_MENU: lambda: [], EventTriggers.ON_CONTEXT_MENU: empty_event,
EventTriggers.ON_DOUBLE_CLICK: lambda: [], EventTriggers.ON_DOUBLE_CLICK: empty_event,
EventTriggers.ON_MOUSE_DOWN: lambda: [], EventTriggers.ON_MOUSE_DOWN: empty_event,
EventTriggers.ON_MOUSE_ENTER: lambda: [], EventTriggers.ON_MOUSE_ENTER: empty_event,
EventTriggers.ON_MOUSE_LEAVE: lambda: [], EventTriggers.ON_MOUSE_LEAVE: empty_event,
EventTriggers.ON_MOUSE_MOVE: lambda: [], EventTriggers.ON_MOUSE_MOVE: empty_event,
EventTriggers.ON_MOUSE_OUT: lambda: [], EventTriggers.ON_MOUSE_OUT: empty_event,
EventTriggers.ON_MOUSE_OVER: lambda: [], EventTriggers.ON_MOUSE_OVER: empty_event,
EventTriggers.ON_MOUSE_UP: lambda: [], EventTriggers.ON_MOUSE_UP: empty_event,
EventTriggers.ON_SCROLL: lambda: [], EventTriggers.ON_SCROLL: empty_event,
EventTriggers.ON_MOUNT: lambda: [], EventTriggers.ON_MOUNT: empty_event,
EventTriggers.ON_UNMOUNT: lambda: [], EventTriggers.ON_UNMOUNT: empty_event,
} }
# Look for component specific triggers, # Look for component specific triggers,
@ -648,7 +649,7 @@ class Component(BaseComponent, ABC):
annotation = field.annotation annotation = field.annotation
if (metadata := getattr(annotation, "__metadata__", None)) is not None: if (metadata := getattr(annotation, "__metadata__", None)) is not None:
args_spec = metadata[0] args_spec = metadata[0]
default_triggers[field.name] = args_spec or (lambda: []) default_triggers[field.name] = args_spec or (empty_event) # type: ignore
return default_triggers return default_triggers
def __repr__(self) -> str: def __repr__(self) -> str:
@ -1705,7 +1706,7 @@ class CustomComponent(Component):
value = self._create_event_chain( value = self._create_event_chain(
value=value, value=value,
args_spec=event_triggers_in_component_declaration.get( args_spec=event_triggers_in_component_declaration.get(
key, lambda: [] key, empty_event
), ),
) )
self.props[format.to_camel_case(key)] = value self.props[format.to_camel_case(key)] = value

View File

@ -139,20 +139,20 @@ class DataEditor(NoSSRComponent):
on_cell_activated: Optional[EventType] = None, on_cell_activated: Optional[EventType] = None,
on_cell_clicked: Optional[EventType] = None, on_cell_clicked: Optional[EventType] = None,
on_cell_context_menu: Optional[EventType] = None, on_cell_context_menu: Optional[EventType] = None,
on_cell_edited: Optional[EventType[[]]] = None, on_cell_edited: Optional[EventType] = None,
on_click: Optional[EventType[[]]] = None, on_click: Optional[EventType[[]]] = None,
on_column_resize: Optional[EventType[[]]] = None, on_column_resize: Optional[EventType] = None,
on_context_menu: Optional[EventType[[]]] = None, on_context_menu: Optional[EventType[[]]] = None,
on_delete: Optional[EventType[[]]] = None, on_delete: Optional[EventType] = None,
on_double_click: Optional[EventType[[]]] = None, on_double_click: Optional[EventType[[]]] = None,
on_finished_editing: Optional[EventType[[]]] = None, on_finished_editing: Optional[EventType] = None,
on_focus: Optional[EventType[[]]] = None, on_focus: Optional[EventType[[]]] = None,
on_group_header_clicked: Optional[EventType[[]]] = None, on_group_header_clicked: Optional[EventType] = None,
on_group_header_context_menu: Optional[EventType[[]]] = None, on_group_header_context_menu: Optional[EventType] = None,
on_group_header_renamed: Optional[EventType[[]]] = None, on_group_header_renamed: Optional[EventType] = None,
on_header_clicked: Optional[EventType] = None, on_header_clicked: Optional[EventType] = None,
on_header_context_menu: Optional[EventType] = None, on_header_context_menu: Optional[EventType] = None,
on_header_menu_click: Optional[EventType[[]]] = None, on_header_menu_click: Optional[EventType] = None,
on_item_hovered: Optional[EventType] = None, on_item_hovered: Optional[EventType] = None,
on_mount: Optional[EventType[[]]] = None, on_mount: Optional[EventType[[]]] = None,
on_mouse_down: Optional[EventType[[]]] = None, on_mouse_down: Optional[EventType[[]]] = None,

View File

@ -58,7 +58,7 @@ class Audio(ReactPlayer):
on_play: Optional[EventType[[]]] = None, on_play: Optional[EventType[[]]] = None,
on_playback_quality_change: Optional[EventType[[]]] = None, on_playback_quality_change: Optional[EventType[[]]] = None,
on_playback_rate_change: Optional[EventType[[]]] = None, on_playback_rate_change: Optional[EventType[[]]] = None,
on_progress: Optional[EventType[[]]] = None, on_progress: Optional[EventType] = None,
on_ready: Optional[EventType[[]]] = None, on_ready: Optional[EventType[[]]] = None,
on_scroll: Optional[EventType[[]]] = None, on_scroll: Optional[EventType[[]]] = None,
on_seek: Optional[EventType] = None, on_seek: Optional[EventType] = None,

View File

@ -56,7 +56,7 @@ class ReactPlayer(NoSSRComponent):
on_play: Optional[EventType[[]]] = None, on_play: Optional[EventType[[]]] = None,
on_playback_quality_change: Optional[EventType[[]]] = None, on_playback_quality_change: Optional[EventType[[]]] = None,
on_playback_rate_change: Optional[EventType[[]]] = None, on_playback_rate_change: Optional[EventType[[]]] = None,
on_progress: Optional[EventType[[]]] = None, on_progress: Optional[EventType] = None,
on_ready: Optional[EventType[[]]] = None, on_ready: Optional[EventType[[]]] = None,
on_scroll: Optional[EventType[[]]] = None, on_scroll: Optional[EventType[[]]] = None,
on_seek: Optional[EventType] = None, on_seek: Optional[EventType] = None,

View File

@ -58,7 +58,7 @@ class Video(ReactPlayer):
on_play: Optional[EventType[[]]] = None, on_play: Optional[EventType[[]]] = None,
on_playback_quality_change: Optional[EventType[[]]] = None, on_playback_quality_change: Optional[EventType[[]]] = None,
on_playback_rate_change: Optional[EventType[[]]] = None, on_playback_rate_change: Optional[EventType[[]]] = None,
on_progress: Optional[EventType[[]]] = None, on_progress: Optional[EventType] = None,
on_ready: Optional[EventType[[]]] = None, on_ready: Optional[EventType[[]]] = None,
on_scroll: Optional[EventType[[]]] = None, on_scroll: Optional[EventType[[]]] = None,
on_seek: Optional[EventType] = None, on_seek: Optional[EventType] = None,

View File

@ -252,7 +252,7 @@ class Brush(Recharts):
A dict mapping the event trigger to the var that is passed to the handler. A dict mapping the event trigger to the var that is passed to the handler.
""" """
return { return {
EventTriggers.ON_CHANGE: lambda: [], EventTriggers.ON_CHANGE: empty_event,
} }
@ -293,10 +293,10 @@ class Cartesian(Recharts):
name: Var[Union[str, int]] name: Var[Union[str, int]]
# The customized event handler of animation start # The customized event handler of animation start
on_animation_start: EventHandler[lambda: []] on_animation_start: EventHandler[empty_event]
# The customized event handler of animation end # The customized event handler of animation end
on_animation_end: EventHandler[lambda: []] on_animation_end: EventHandler[empty_event]
# The customized event handler of click on the component in this group # The customized event handler of click on the component in this group
on_click: EventHandler[empty_event] on_click: EventHandler[empty_event]

View File

@ -330,9 +330,9 @@ class RadarChart(ChartBase):
A dict mapping the event trigger to the var that is passed to the handler. A dict mapping the event trigger to the var that is passed to the handler.
""" """
return { return {
EventTriggers.ON_CLICK: lambda: [], EventTriggers.ON_CLICK: empty_event,
EventTriggers.ON_MOUSE_ENTER: lambda: [], EventTriggers.ON_MOUSE_ENTER: empty_event,
EventTriggers.ON_MOUSE_LEAVE: lambda: [], EventTriggers.ON_MOUSE_LEAVE: empty_event,
} }
@ -419,14 +419,14 @@ class ScatterChart(ChartBase):
A dict mapping the event trigger to the var that is passed to the handler. A dict mapping the event trigger to the var that is passed to the handler.
""" """
return { return {
EventTriggers.ON_CLICK: lambda: [], EventTriggers.ON_CLICK: empty_event,
EventTriggers.ON_MOUSE_DOWN: lambda: [], EventTriggers.ON_MOUSE_DOWN: empty_event,
EventTriggers.ON_MOUSE_UP: lambda: [], EventTriggers.ON_MOUSE_UP: empty_event,
EventTriggers.ON_MOUSE_MOVE: lambda: [], EventTriggers.ON_MOUSE_MOVE: empty_event,
EventTriggers.ON_MOUSE_OVER: lambda: [], EventTriggers.ON_MOUSE_OVER: empty_event,
EventTriggers.ON_MOUSE_OUT: lambda: [], EventTriggers.ON_MOUSE_OUT: empty_event,
EventTriggers.ON_MOUSE_ENTER: lambda: [], EventTriggers.ON_MOUSE_ENTER: empty_event,
EventTriggers.ON_MOUSE_LEAVE: lambda: [], EventTriggers.ON_MOUSE_LEAVE: empty_event,
} }

View File

@ -103,14 +103,14 @@ class Pie(Recharts):
A dict mapping the event trigger to the var that is passed to the handler. A dict mapping the event trigger to the var that is passed to the handler.
""" """
return { return {
EventTriggers.ON_ANIMATION_START: lambda: [], EventTriggers.ON_ANIMATION_START: empty_event,
EventTriggers.ON_ANIMATION_END: lambda: [], EventTriggers.ON_ANIMATION_END: empty_event,
EventTriggers.ON_CLICK: lambda: [], EventTriggers.ON_CLICK: empty_event,
EventTriggers.ON_MOUSE_MOVE: lambda: [], EventTriggers.ON_MOUSE_MOVE: empty_event,
EventTriggers.ON_MOUSE_OVER: lambda: [], EventTriggers.ON_MOUSE_OVER: empty_event,
EventTriggers.ON_MOUSE_OUT: lambda: [], EventTriggers.ON_MOUSE_OUT: empty_event,
EventTriggers.ON_MOUSE_ENTER: lambda: [], EventTriggers.ON_MOUSE_ENTER: empty_event,
EventTriggers.ON_MOUSE_LEAVE: lambda: [], EventTriggers.ON_MOUSE_LEAVE: empty_event,
} }
@ -167,8 +167,8 @@ class Radar(Recharts):
A dict mapping the event trigger to the var that is passed to the handler. A dict mapping the event trigger to the var that is passed to the handler.
""" """
return { return {
EventTriggers.ON_ANIMATION_START: lambda: [], EventTriggers.ON_ANIMATION_START: empty_event,
EventTriggers.ON_ANIMATION_END: lambda: [], EventTriggers.ON_ANIMATION_END: empty_event,
} }
@ -219,14 +219,14 @@ class RadialBar(Recharts):
A dict mapping the event trigger to the var that is passed to the handler. A dict mapping the event trigger to the var that is passed to the handler.
""" """
return { return {
EventTriggers.ON_CLICK: lambda: [], EventTriggers.ON_CLICK: empty_event,
EventTriggers.ON_MOUSE_MOVE: lambda: [], EventTriggers.ON_MOUSE_MOVE: empty_event,
EventTriggers.ON_MOUSE_OVER: lambda: [], EventTriggers.ON_MOUSE_OVER: empty_event,
EventTriggers.ON_MOUSE_OUT: lambda: [], EventTriggers.ON_MOUSE_OUT: empty_event,
EventTriggers.ON_MOUSE_ENTER: lambda: [], EventTriggers.ON_MOUSE_ENTER: empty_event,
EventTriggers.ON_MOUSE_LEAVE: lambda: [], EventTriggers.ON_MOUSE_LEAVE: empty_event,
EventTriggers.ON_ANIMATION_START: lambda: [], EventTriggers.ON_ANIMATION_START: empty_event,
EventTriggers.ON_ANIMATION_END: lambda: [], EventTriggers.ON_ANIMATION_END: empty_event,
} }
@ -392,12 +392,12 @@ class PolarRadiusAxis(Recharts):
A dict mapping the event trigger to the var that is passed to the handler. A dict mapping the event trigger to the var that is passed to the handler.
""" """
return { return {
EventTriggers.ON_CLICK: lambda: [], EventTriggers.ON_CLICK: empty_event,
EventTriggers.ON_MOUSE_MOVE: lambda: [], EventTriggers.ON_MOUSE_MOVE: empty_event,
EventTriggers.ON_MOUSE_OVER: lambda: [], EventTriggers.ON_MOUSE_OVER: empty_event,
EventTriggers.ON_MOUSE_OUT: lambda: [], EventTriggers.ON_MOUSE_OUT: empty_event,
EventTriggers.ON_MOUSE_ENTER: lambda: [], EventTriggers.ON_MOUSE_ENTER: empty_event,
EventTriggers.ON_MOUSE_LEAVE: lambda: [], EventTriggers.ON_MOUSE_LEAVE: empty_event,
} }

View File

@ -122,16 +122,16 @@ class Editor(NoSSRComponent):
class_name: Optional[Any] = None, class_name: Optional[Any] = None,
autofocus: Optional[bool] = None, autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None, on_blur: Optional[EventType] = None,
on_change: Optional[EventType[[]]] = None, on_change: Optional[EventType] = None,
on_click: Optional[EventType[[]]] = None, on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None, on_context_menu: Optional[EventType[[]]] = None,
on_copy: Optional[EventType[[]]] = None, on_copy: Optional[EventType] = None,
on_cut: Optional[EventType[[]]] = None, on_cut: Optional[EventType] = None,
on_double_click: Optional[EventType[[]]] = None, on_double_click: Optional[EventType[[]]] = None,
on_focus: Optional[EventType[[]]] = None, on_focus: Optional[EventType[[]]] = None,
on_input: Optional[EventType[[]]] = None, on_input: Optional[EventType] = None,
on_load: Optional[EventType[[]]] = None, on_load: Optional[EventType] = None,
on_mount: Optional[EventType[[]]] = None, on_mount: Optional[EventType[[]]] = None,
on_mouse_down: Optional[EventType[[]]] = None, on_mouse_down: Optional[EventType[[]]] = None,
on_mouse_enter: Optional[EventType[[]]] = None, on_mouse_enter: Optional[EventType[[]]] = None,
@ -140,12 +140,12 @@ class Editor(NoSSRComponent):
on_mouse_out: Optional[EventType[[]]] = None, on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None, on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None, on_mouse_up: Optional[EventType[[]]] = None,
on_paste: Optional[EventType[[]]] = None, on_paste: Optional[EventType] = None,
on_resize_editor: Optional[EventType[[]]] = None, on_resize_editor: Optional[EventType] = None,
on_scroll: Optional[EventType[[]]] = None, on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None, on_unmount: Optional[EventType[[]]] = None,
toggle_code_view: Optional[EventType[[]]] = None, toggle_code_view: Optional[EventType] = None,
toggle_full_screen: Optional[EventType[[]]] = None, toggle_full_screen: Optional[EventType] = None,
**props, **props,
) -> "Editor": ) -> "Editor":
"""Create an instance of Editor. No children allowed. """Create an instance of Editor. No children allowed.

View File

@ -22,6 +22,7 @@ from typing import (
TypeVar, TypeVar,
Union, Union,
get_type_hints, get_type_hints,
overload,
) )
from typing_extensions import ParamSpec, get_args, get_origin from typing_extensions import ParamSpec, get_args, get_origin
@ -32,14 +33,17 @@ from reflex.utils.exceptions import EventFnArgMismatch, EventHandlerArgMismatch
from reflex.utils.types import ArgsSpec, GenericType from reflex.utils.types import ArgsSpec, GenericType
from reflex.vars import VarData from reflex.vars import VarData
from reflex.vars.base import ( from reflex.vars.base import (
CachedVarOperation,
LiteralNoneVar, LiteralNoneVar,
LiteralVar, LiteralVar,
ToOperation, ToOperation,
Var, Var,
cached_property_no_lock,
) )
from reflex.vars.function import ArgsFunctionOperation, FunctionStringVar, FunctionVar from reflex.vars.function import (
ArgsFunctionOperation,
FunctionStringVar,
FunctionVar,
VarOperationCall,
)
from reflex.vars.object import ObjectVar from reflex.vars.object import ObjectVar
try: try:
@ -395,11 +399,6 @@ class EventChain(EventActionsMixin):
invocation: Optional[Var] = dataclasses.field(default=None) invocation: Optional[Var] = dataclasses.field(default=None)
# These chains can be used for their side effects when no other events are desired.
stop_propagation = EventChain(events=[], args_spec=lambda: []).stop_propagation
prevent_default = EventChain(events=[], args_spec=lambda: []).prevent_default
@dataclasses.dataclass( @dataclasses.dataclass(
init=True, init=True,
frozen=True, frozen=True,
@ -463,6 +462,11 @@ def empty_event() -> Tuple[()]:
return tuple() # type: ignore return tuple() # type: ignore
# These chains can be used for their side effects when no other events are desired.
stop_propagation = EventChain(events=[], args_spec=empty_event).stop_propagation
prevent_default = EventChain(events=[], args_spec=empty_event).prevent_default
T = TypeVar("T") T = TypeVar("T")
@ -1041,7 +1045,8 @@ def resolve_annotation(annotations: dict[str, Any], arg_name: str):
deprecation_version="0.6.3", deprecation_version="0.6.3",
removal_version="0.7.0", removal_version="0.7.0",
) )
return JavascriptInputEvent # Allow arbitrary attribute access two levels deep until removed.
return Dict[str, dict]
return annotation return annotation
@ -1258,7 +1263,7 @@ class EventVar(ObjectVar):
frozen=True, frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {}, **{"slots": True} if sys.version_info >= (3, 10) else {},
) )
class LiteralEventVar(CachedVarOperation, LiteralVar, EventVar): class LiteralEventVar(VarOperationCall, LiteralVar, EventVar):
"""A literal event var.""" """A literal event var."""
_var_value: EventSpec = dataclasses.field(default=None) # type: ignore _var_value: EventSpec = dataclasses.field(default=None) # type: ignore
@ -1271,35 +1276,6 @@ class LiteralEventVar(CachedVarOperation, LiteralVar, EventVar):
""" """
return hash((self.__class__.__name__, self._js_expr)) return hash((self.__class__.__name__, self._js_expr))
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the var.
Returns:
The name of the var.
"""
return str(
FunctionStringVar("Event").call(
# event handler name
".".join(
filter(
None,
format.get_event_handler_parts(self._var_value.handler),
)
),
# event handler args
{str(name): value for name, value in self._var_value.args},
# event actions
self._var_value.event_actions,
# client handler name
*(
[self._var_value.client_handler_name]
if self._var_value.client_handler_name
else []
),
)
)
@classmethod @classmethod
def create( def create(
cls, cls,
@ -1320,6 +1296,22 @@ class LiteralEventVar(CachedVarOperation, LiteralVar, EventVar):
_var_type=EventSpec, _var_type=EventSpec,
_var_data=_var_data, _var_data=_var_data,
_var_value=value, _var_value=value,
_func=FunctionStringVar("Event"),
_args=(
# event handler name
".".join(
filter(
None,
format.get_event_handler_parts(value.handler),
)
),
# event handler args
{str(name): value for name, value in value.args},
# event actions
value.event_actions,
# client handler name
*([value.client_handler_name] if value.client_handler_name else []),
),
) )
@ -1332,7 +1324,10 @@ class EventChainVar(FunctionVar):
frozen=True, frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {}, **{"slots": True} if sys.version_info >= (3, 10) else {},
) )
class LiteralEventChainVar(CachedVarOperation, LiteralVar, EventChainVar): # Note: LiteralVar is second in the inheritance list allowing it act like a
# CachedVarOperation (ArgsFunctionOperation) and get the _js_expr from the
# _cached_var_name property.
class LiteralEventChainVar(ArgsFunctionOperation, LiteralVar, EventChainVar):
"""A literal event chain var.""" """A literal event chain var."""
_var_value: EventChain = dataclasses.field(default=None) # type: ignore _var_value: EventChain = dataclasses.field(default=None) # type: ignore
@ -1345,41 +1340,6 @@ class LiteralEventChainVar(CachedVarOperation, LiteralVar, EventChainVar):
""" """
return hash((self.__class__.__name__, self._js_expr)) return hash((self.__class__.__name__, self._js_expr))
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the var.
Returns:
The name of the var.
"""
sig = inspect.signature(self._var_value.args_spec) # type: ignore
if sig.parameters:
arg_def = tuple((f"_{p}" for p in sig.parameters))
arg_def_expr = LiteralVar.create([Var(_js_expr=arg) for arg in arg_def])
else:
# add a default argument for addEvents if none were specified in value.args_spec
# used to trigger the preventDefault() on the event.
arg_def = ("...args",)
arg_def_expr = Var(_js_expr="args")
if self._var_value.invocation is None:
invocation = FunctionStringVar.create("addEvents")
else:
invocation = self._var_value.invocation
return str(
ArgsFunctionOperation.create(
arg_def,
invocation.call(
LiteralVar.create(
[LiteralVar.create(event) for event in self._var_value.events]
),
arg_def_expr,
self._var_value.event_actions,
),
)
)
@classmethod @classmethod
def create( def create(
cls, cls,
@ -1395,10 +1355,31 @@ class LiteralEventChainVar(CachedVarOperation, LiteralVar, EventChainVar):
Returns: Returns:
The created LiteralEventChainVar instance. The created LiteralEventChainVar instance.
""" """
sig = inspect.signature(value.args_spec) # type: ignore
if sig.parameters:
arg_def = tuple((f"_{p}" for p in sig.parameters))
arg_def_expr = LiteralVar.create([Var(_js_expr=arg) for arg in arg_def])
else:
# add a default argument for addEvents if none were specified in value.args_spec
# used to trigger the preventDefault() on the event.
arg_def = ("...args",)
arg_def_expr = Var(_js_expr="args")
if value.invocation is None:
invocation = FunctionStringVar.create("addEvents")
else:
invocation = value.invocation
return cls( return cls(
_js_expr="", _js_expr="",
_var_type=EventChain, _var_type=EventChain,
_var_data=_var_data, _var_data=_var_data,
_args_names=arg_def,
_return_expr=invocation.call(
LiteralVar.create([LiteralVar.create(event) for event in value.events]),
arg_def_expr,
value.event_actions,
),
_var_value=value, _var_value=value,
) )
@ -1431,12 +1412,17 @@ class ToEventChainVarOperation(ToOperation, EventChainVar):
G = ParamSpec("G") G = ParamSpec("G")
IndividualEventType = Union[EventSpec, EventHandler, Callable[G, Any], Var] IndividualEventType = Union[EventSpec, EventHandler, Callable[G, Any], Var[Any]]
EventType = Union[IndividualEventType[G], List[IndividualEventType[G]]] EventType = Union[IndividualEventType[G], List[IndividualEventType[G]]]
P = ParamSpec("P") P = ParamSpec("P")
T = TypeVar("T") T = TypeVar("T")
V = TypeVar("V")
V2 = TypeVar("V2")
V3 = TypeVar("V3")
V4 = TypeVar("V4")
V5 = TypeVar("V5")
if sys.version_info >= (3, 10): if sys.version_info >= (3, 10):
from typing import Concatenate from typing import Concatenate
@ -1452,7 +1438,55 @@ if sys.version_info >= (3, 10):
""" """
self.func = func self.func = func
def __get__(self, instance, owner) -> Callable[P, T]: @overload
def __get__(
self: EventCallback[[V], T], instance: None, owner
) -> Callable[[Union[Var[V], V]], EventSpec]: ...
@overload
def __get__(
self: EventCallback[[V, V2], T], instance: None, owner
) -> Callable[[Union[Var[V], V], Union[Var[V2], V2]], EventSpec]: ...
@overload
def __get__(
self: EventCallback[[V, V2, V3], T], instance: None, owner
) -> Callable[
[Union[Var[V], V], Union[Var[V2], V2], Union[Var[V3], V3]],
EventSpec,
]: ...
@overload
def __get__(
self: EventCallback[[V, V2, V3, V4], T], instance: None, owner
) -> Callable[
[
Union[Var[V], V],
Union[Var[V2], V2],
Union[Var[V3], V3],
Union[Var[V4], V4],
],
EventSpec,
]: ...
@overload
def __get__(
self: EventCallback[[V, V2, V3, V4, V5], T], instance: None, owner
) -> Callable[
[
Union[Var[V], V],
Union[Var[V2], V2],
Union[Var[V3], V3],
Union[Var[V4], V4],
Union[Var[V5], V5],
],
EventSpec,
]: ...
@overload
def __get__(self, instance, owner) -> Callable[P, T]: ...
def __get__(self, instance, owner) -> Callable:
"""Get the function with the instance bound to it. """Get the function with the instance bound to it.
Args: Args:

View File

@ -2566,9 +2566,11 @@ class StateManager(Base, ABC):
The state manager (either disk, memory or redis). The state manager (either disk, memory or redis).
""" """
config = get_config() config = get_config()
if config.state_manager_mode == constants.StateManagerMode.DISK: if prerequisites.parse_redis_url() is not None:
return StateManagerMemory(state=state) config.state_manager_mode = constants.StateManagerMode.REDIS
if config.state_manager_mode == constants.StateManagerMode.MEMORY: if config.state_manager_mode == constants.StateManagerMode.MEMORY:
return StateManagerMemory(state=state)
if config.state_manager_mode == constants.StateManagerMode.DISK:
return StateManagerDisk(state=state) return StateManagerDisk(state=state)
if config.state_manager_mode == constants.StateManagerMode.REDIS: if config.state_manager_mode == constants.StateManagerMode.REDIS:
redis = prerequisites.get_redis() redis = prerequisites.get_redis()
@ -2840,9 +2842,13 @@ class StateManagerDisk(StateManager):
for substate in state.get_substates(): for substate in state.get_substates():
substate_token = _substate_key(client_token, substate) substate_token = _substate_key(client_token, substate)
fresh_instance = await root_state.get_state(substate)
instance = await self.load_state(substate_token) instance = await self.load_state(substate_token)
if instance is None: if instance is not None:
instance = await root_state.get_state(substate) # Ensure all substates exist, even if they weren't serialized previously.
instance.substates = fresh_instance.substates
else:
instance = fresh_instance
state.substates[substate.get_name()] = instance state.substates[substate.get_name()] = instance
instance.parent_state = state instance.parent_state = state

View File

@ -10,6 +10,7 @@ from reflex.event import EventChain, EventHandler
from reflex.utils import format from reflex.utils import format
from reflex.utils.exceptions import ReflexError from reflex.utils.exceptions import ReflexError
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
from reflex.utils.types import get_origin
from reflex.vars import VarData from reflex.vars import VarData
from reflex.vars.base import CallableVar, LiteralVar, Var from reflex.vars.base import CallableVar, LiteralVar, Var
from reflex.vars.function import FunctionVar from reflex.vars.function import FunctionVar
@ -196,6 +197,10 @@ def convert(
isinstance(value, Breakpoints) isinstance(value, Breakpoints)
and all(not isinstance(v, dict) for v in value.values()) and all(not isinstance(v, dict) for v in value.values())
) )
or (
isinstance(value, ObjectVar)
and not issubclass(get_origin(value._var_type) or value._var_type, dict)
)
else (key,) else (key,)
) )

View File

@ -429,7 +429,7 @@ def _generate_component_create_functiondef(
def figure_out_return_type(annotation: Any): def figure_out_return_type(annotation: Any):
if inspect.isclass(annotation) and issubclass(annotation, inspect._empty): if inspect.isclass(annotation) and issubclass(annotation, inspect._empty):
return ast.Name(id="Optional[EventType[[]]]") return ast.Name(id="Optional[EventType]")
if isinstance(annotation, str) and annotation.startswith("Tuple["): if isinstance(annotation, str) and annotation.startswith("Tuple["):
inside_of_tuple = annotation.removeprefix("Tuple[").removesuffix("]") inside_of_tuple = annotation.removeprefix("Tuple[").removesuffix("]")

View File

@ -1155,7 +1155,7 @@ class ArrayVar(Var[ARRAY_VAR_TYPE]):
function_var = ArgsFunctionOperation.create(tuple(), return_value) function_var = ArgsFunctionOperation.create(tuple(), return_value)
else: else:
# generic number var # generic number var
number_var = Var("").to(NumberVar) number_var = Var("").to(NumberVar, int)
first_arg_type = self[number_var]._var_type first_arg_type = self[number_var]._var_type
@ -1167,7 +1167,10 @@ class ArrayVar(Var[ARRAY_VAR_TYPE]):
_var_type=first_arg_type, _var_type=first_arg_type,
).guess_type() ).guess_type()
function_var = ArgsFunctionOperation.create((arg_name,), fn(first_arg)) function_var = ArgsFunctionOperation.create(
(arg_name,),
Var.create(fn(first_arg)),
)
return map_array_operation(self, function_var) return map_array_operation(self, function_var)

View File

@ -2,11 +2,18 @@ from typing import List
import pytest import pytest
from reflex import event from reflex.event import (
from reflex.event import Event, EventHandler, EventSpec, call_event_handler, fix_events Event,
EventChain,
EventHandler,
EventSpec,
call_event_handler,
event,
fix_events,
)
from reflex.state import BaseState from reflex.state import BaseState
from reflex.utils import format from reflex.utils import format
from reflex.vars.base import LiteralVar, Var from reflex.vars.base import Field, LiteralVar, Var, field
def make_var(value) -> Var: def make_var(value) -> Var:
@ -388,3 +395,28 @@ def test_event_actions_on_state():
assert sp_handler.event_actions == {"stopPropagation": True} assert sp_handler.event_actions == {"stopPropagation": True}
# should NOT affect other references to the handler # should NOT affect other references to the handler
assert not handler.event_actions assert not handler.event_actions
def test_event_var_data():
class S(BaseState):
x: Field[int] = field(0)
@event
def s(self, value: int):
pass
# Handler doesn't have any _var_data because it's just a str
handler_var = Var.create(S.s)
assert handler_var._get_all_var_data() is None
# Ensure spec carries _var_data
spec_var = Var.create(S.s(S.x))
assert spec_var._get_all_var_data() == S.x._get_all_var_data()
# Needed to instantiate the EventChain
def _args_spec(value: Var[int]) -> tuple[Var[int]]:
return (value,)
# Ensure chain carries _var_data
chain_var = Var.create(EventChain(events=[S.s(S.x)], args_spec=_args_spec))
assert chain_var._get_all_var_data() == S.x._get_all_var_data()

View File

@ -3313,3 +3313,36 @@ def test_assignment_to_undeclared_vars():
state.handle_supported_regular_vars() state.handle_supported_regular_vars()
state.handle_non_var() state.handle_non_var()
@pytest.mark.asyncio
async def test_deserialize_gc_state_disk(token):
"""Test that a state can be deserialized from disk with a grandchild state.
Args:
token: A token.
"""
class Root(BaseState):
pass
class State(Root):
num: int = 42
class Child(State):
foo: str = "bar"
dsm = StateManagerDisk(state=Root)
async with dsm.modify_state(token) as root:
s = await root.get_state(State)
s.num += 1
c = await root.get_state(Child)
assert s._get_was_touched()
assert not c._get_was_touched()
dsm2 = StateManagerDisk(state=Root)
root = await dsm2.get_state(token)
s = await root.get_state(State)
assert s.num == 43
c = await root.get_state(Child)
assert c.foo == "bar"