reflex 0.5.3a2__py3-none-any.whl → 0.5.4a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of reflex might be problematic. Click here for more details.

Files changed (50) hide show
  1. reflex/.templates/apps/demo/code/webui/state.py +3 -2
  2. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +19 -20
  3. reflex/.templates/web/utils/state.js +6 -0
  4. reflex/__init__.py +7 -1
  5. reflex/__init__.pyi +2 -0
  6. reflex/app.py +2 -5
  7. reflex/compiler/compiler.py +2 -2
  8. reflex/components/component.py +19 -6
  9. reflex/components/core/client_side_routing.py +2 -2
  10. reflex/components/core/client_side_routing.pyi +1 -0
  11. reflex/components/core/upload.py +1 -1
  12. reflex/components/datadisplay/dataeditor.py +7 -2
  13. reflex/components/datadisplay/dataeditor.pyi +1 -0
  14. reflex/components/el/elements/forms.py +18 -11
  15. reflex/components/el/elements/forms.pyi +1 -0
  16. reflex/components/markdown/markdown.py +1 -1
  17. reflex/components/plotly/plotly.py +76 -12
  18. reflex/components/plotly/plotly.pyi +15 -82
  19. reflex/components/radix/themes/base.py +9 -2
  20. reflex/components/radix/themes/base.pyi +1 -0
  21. reflex/components/recharts/cartesian.py +42 -14
  22. reflex/components/recharts/cartesian.pyi +81 -17
  23. reflex/components/recharts/charts.py +12 -21
  24. reflex/components/recharts/charts.pyi +53 -14
  25. reflex/components/sonner/toast.py +30 -14
  26. reflex/components/sonner/toast.pyi +8 -4
  27. reflex/config.py +22 -14
  28. reflex/constants/__init__.py +2 -0
  29. reflex/constants/config.py +7 -0
  30. reflex/event.py +12 -6
  31. reflex/experimental/__init__.py +22 -2
  32. reflex/experimental/client_state.py +81 -23
  33. reflex/experimental/hooks.py +29 -35
  34. reflex/experimental/layout.py +8 -3
  35. reflex/experimental/layout.pyi +536 -0
  36. reflex/reflex.py +9 -5
  37. reflex/style.py +1 -0
  38. reflex/testing.py +44 -13
  39. reflex/utils/format.py +8 -1
  40. reflex/utils/processes.py +27 -0
  41. reflex/utils/pyi_generator.py +11 -4
  42. reflex/utils/serializers.py +114 -15
  43. reflex/utils/types.py +6 -2
  44. reflex/vars.py +39 -10
  45. reflex/vars.pyi +2 -2
  46. {reflex-0.5.3a2.dist-info → reflex-0.5.4a1.dist-info}/METADATA +1 -1
  47. {reflex-0.5.3a2.dist-info → reflex-0.5.4a1.dist-info}/RECORD +50 -49
  48. {reflex-0.5.3a2.dist-info → reflex-0.5.4a1.dist-info}/LICENSE +0 -0
  49. {reflex-0.5.3a2.dist-info → reflex-0.5.4a1.dist-info}/WHEEL +0 -0
  50. {reflex-0.5.3a2.dist-info → reflex-0.5.4a1.dist-info}/entry_points.txt +0 -0
@@ -11,6 +11,7 @@ from typing import Any, Dict, List, Union
11
11
  from reflex.components.component import Component
12
12
  from reflex.components.recharts.general import ResponsiveContainer
13
13
  from reflex.constants import EventTriggers
14
+ from reflex.event import EventHandler
14
15
  from reflex.vars import Var
15
16
  from .recharts import (
16
17
  LiteralAnimationEasing,
@@ -105,12 +106,6 @@ class AreaChart(ChartBase):
105
106
  Union[int, Literal["dataMin", "dataMax", "auto"]],
106
107
  ]
107
108
  ] = None,
108
- stack_offset: Optional[
109
- Union[
110
- Var[Literal["expand", "none", "wiggle", "silhouette"]],
111
- Literal["expand", "none", "wiggle", "silhouette"],
112
- ]
113
- ] = None,
114
109
  data: Optional[Union[Var[List[Dict[str, Any]]], List[Dict[str, Any]]]] = None,
115
110
  sync_id: Optional[Union[Var[str], str]] = None,
116
111
  sync_method: Optional[
@@ -125,6 +120,12 @@ class AreaChart(ChartBase):
125
120
  ]
126
121
  ] = None,
127
122
  margin: Optional[Union[Var[Dict[str, Any]], Dict[str, Any]]] = None,
123
+ stack_offset: Optional[
124
+ Union[
125
+ Var[Literal["expand", "none", "wiggle", "silhouette"]],
126
+ Literal["expand", "none", "wiggle", "silhouette"],
127
+ ]
128
+ ] = None,
128
129
  style: Optional[Style] = None,
129
130
  key: Optional[Any] = None,
130
131
  id: Optional[Any] = None,
@@ -150,7 +151,6 @@ class AreaChart(ChartBase):
150
151
  Args:
151
152
  *children: The children of the chart component.
152
153
  base_value: The base value of area. Number | 'dataMin' | 'dataMax' | 'auto'
153
- stack_offset: The type of offset function used to generate the lower and upper values in the series array. The four types are built-in offsets in d3-shape. 'expand' | 'none' | 'wiggle' | 'silhouette'
154
154
  data: The source data, in which each element is an object.
155
155
  sync_id: If any two categorical charts(rx.line_chart, rx.area_chart, rx.bar_chart, rx.composed_chart) have the same sync_id, these two charts can sync the position GraphingTooltip, and the start_index, end_index of Brush.
156
156
  sync_method: When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function
@@ -158,6 +158,7 @@ class AreaChart(ChartBase):
158
158
  height: The height of chart container.
159
159
  layout: The layout of area in the chart. 'horizontal' | 'vertical'
160
160
  margin: The sizes of whitespace around the chart.
161
+ stack_offset: The type of offset function used to generate the lower and upper values in the series array. The four types are built-in offsets in d3-shape. 'expand' | 'none' | 'wiggle' | 'silhouette'
161
162
  style: The style of the component.
162
163
  key: A unique key for the component.
163
164
  id: The id for the component.
@@ -526,9 +527,6 @@ class RadarChart(ChartBase):
526
527
  on_mouse_leave: Optional[
527
528
  Union[EventHandler, EventSpec, list, function, BaseVar]
528
529
  ] = None,
529
- on_mouse_move: Optional[
530
- Union[EventHandler, EventSpec, list, function, BaseVar]
531
- ] = None,
532
530
  **props
533
531
  ) -> "RadarChart":
534
532
  """Create a chart component.
@@ -613,9 +611,6 @@ class RadialBarChart(ChartBase):
613
611
  on_mouse_leave: Optional[
614
612
  Union[EventHandler, EventSpec, list, function, BaseVar]
615
613
  ] = None,
616
- on_mouse_move: Optional[
617
- Union[EventHandler, EventSpec, list, function, BaseVar]
618
- ] = None,
619
614
  **props
620
615
  ) -> "RadialBarChart":
621
616
  """Create a chart component.
@@ -688,6 +683,9 @@ class ScatterChart(ChartBase):
688
683
  on_click: Optional[
689
684
  Union[EventHandler, EventSpec, list, function, BaseVar]
690
685
  ] = None,
686
+ on_mouse_down: Optional[
687
+ Union[EventHandler, EventSpec, list, function, BaseVar]
688
+ ] = None,
691
689
  on_mouse_enter: Optional[
692
690
  Union[EventHandler, EventSpec, list, function, BaseVar]
693
691
  ] = None,
@@ -703,6 +701,9 @@ class ScatterChart(ChartBase):
703
701
  on_mouse_over: Optional[
704
702
  Union[EventHandler, EventSpec, list, function, BaseVar]
705
703
  ] = None,
704
+ on_mouse_up: Optional[
705
+ Union[EventHandler, EventSpec, list, function, BaseVar]
706
+ ] = None,
706
707
  **props
707
708
  ) -> "ScatterChart":
708
709
  """Create a chart component.
@@ -731,7 +732,6 @@ class ScatterChart(ChartBase):
731
732
  ...
732
733
 
733
734
  class FunnelChart(RechartsCharts):
734
- def get_event_triggers(self) -> dict[str, Union[Var, Any]]: ...
735
735
  @overload
736
736
  @classmethod
737
737
  def create( # type: ignore
@@ -756,9 +756,27 @@ class FunnelChart(RechartsCharts):
756
756
  class_name: Optional[Any] = None,
757
757
  autofocus: Optional[bool] = None,
758
758
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
759
+ on_blur: Optional[
760
+ Union[EventHandler, EventSpec, list, function, BaseVar]
761
+ ] = None,
759
762
  on_click: Optional[
760
763
  Union[EventHandler, EventSpec, list, function, BaseVar]
761
764
  ] = None,
765
+ on_context_menu: Optional[
766
+ Union[EventHandler, EventSpec, list, function, BaseVar]
767
+ ] = None,
768
+ on_double_click: Optional[
769
+ Union[EventHandler, EventSpec, list, function, BaseVar]
770
+ ] = None,
771
+ on_focus: Optional[
772
+ Union[EventHandler, EventSpec, list, function, BaseVar]
773
+ ] = None,
774
+ on_mount: Optional[
775
+ Union[EventHandler, EventSpec, list, function, BaseVar]
776
+ ] = None,
777
+ on_mouse_down: Optional[
778
+ Union[EventHandler, EventSpec, list, function, BaseVar]
779
+ ] = None,
762
780
  on_mouse_enter: Optional[
763
781
  Union[EventHandler, EventSpec, list, function, BaseVar]
764
782
  ] = None,
@@ -768,6 +786,21 @@ class FunnelChart(RechartsCharts):
768
786
  on_mouse_move: Optional[
769
787
  Union[EventHandler, EventSpec, list, function, BaseVar]
770
788
  ] = None,
789
+ on_mouse_out: Optional[
790
+ Union[EventHandler, EventSpec, list, function, BaseVar]
791
+ ] = None,
792
+ on_mouse_over: Optional[
793
+ Union[EventHandler, EventSpec, list, function, BaseVar]
794
+ ] = None,
795
+ on_mouse_up: Optional[
796
+ Union[EventHandler, EventSpec, list, function, BaseVar]
797
+ ] = None,
798
+ on_scroll: Optional[
799
+ Union[EventHandler, EventSpec, list, function, BaseVar]
800
+ ] = None,
801
+ on_unmount: Optional[
802
+ Union[EventHandler, EventSpec, list, function, BaseVar]
803
+ ] = None,
771
804
  **props
772
805
  ) -> "FunnelChart":
773
806
  """Create a new memoization leaf component.
@@ -821,6 +854,12 @@ class Treemap(RechartsCharts):
821
854
  class_name: Optional[Any] = None,
822
855
  autofocus: Optional[bool] = None,
823
856
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
857
+ on_animation_end: Optional[
858
+ Union[EventHandler, EventSpec, list, function, BaseVar]
859
+ ] = None,
860
+ on_animation_start: Optional[
861
+ Union[EventHandler, EventSpec, list, function, BaseVar]
862
+ ] = None,
824
863
  on_blur: Optional[
825
864
  Union[EventHandler, EventSpec, list, function, BaseVar]
826
865
  ] = None,
@@ -106,7 +106,7 @@ class ToastProps(PropsBase):
106
106
  cancel: Optional[ToastAction]
107
107
 
108
108
  # Custom id for the toast.
109
- id: Optional[str]
109
+ id: Optional[Union[str, Var]]
110
110
 
111
111
  # Removes the default styling, which allows for easier customization.
112
112
  unstyled: Optional[bool]
@@ -127,7 +127,7 @@ class ToastProps(PropsBase):
127
127
  # Function that gets called when the toast disappears automatically after it's timeout (duration` prop).
128
128
  on_auto_close: Optional[Any]
129
129
 
130
- def dict(self, *args, **kwargs) -> dict:
130
+ def dict(self, *args, **kwargs) -> dict[str, Any]:
131
131
  """Convert the object to a dictionary.
132
132
 
133
133
  Args:
@@ -137,7 +137,7 @@ class ToastProps(PropsBase):
137
137
  Returns:
138
138
  The object as a dictionary with ToastAction fields intact.
139
139
  """
140
- kwargs.setdefault("exclude_none", True)
140
+ kwargs.setdefault("exclude_none", True) # type: ignore
141
141
  d = super().dict(*args, **kwargs)
142
142
  # Keep these fields as ToastAction so they can be serialized specially
143
143
  if "action" in d:
@@ -208,7 +208,12 @@ class Toaster(Component):
208
208
  # Pauses toast timers when the page is hidden, e.g., when the tab is backgrounded, the browser is minimized, or the OS is locked.
209
209
  pause_when_page_is_hidden: Var[bool]
210
210
 
211
- def _get_hooks(self) -> Var[str]:
211
+ def add_hooks(self) -> list[Var | str]:
212
+ """Add hooks for the toaster component.
213
+
214
+ Returns:
215
+ The hooks for the toaster component.
216
+ """
212
217
  hook = Var.create_safe(
213
218
  f"{toast_ref} = toast",
214
219
  _var_is_local=True,
@@ -219,7 +224,7 @@ class Toaster(Component):
219
224
  }
220
225
  ),
221
226
  )
222
- return hook
227
+ return [hook]
223
228
 
224
229
  @staticmethod
225
230
  def send_toast(message: str, level: str | None = None, **props) -> EventSpec:
@@ -235,13 +240,13 @@ class Toaster(Component):
235
240
  """
236
241
  toast_command = f"{toast_ref}.{level}" if level is not None else toast_ref
237
242
  if props:
238
- args = serialize(ToastProps(**props))
243
+ args = serialize(ToastProps(**props)) # type: ignore
239
244
  toast = f"{toast_command}(`{message}`, {args})"
240
245
  else:
241
246
  toast = f"{toast_command}(`{message}`)"
242
247
 
243
- toast_action = Var.create(toast, _var_is_string=False, _var_is_local=True)
244
- return call_script(toast_action) # type: ignore
248
+ toast_action = Var.create_safe(toast, _var_is_string=False, _var_is_local=True)
249
+ return call_script(toast_action)
245
250
 
246
251
  @staticmethod
247
252
  def toast_info(message: str, **kwargs):
@@ -295,7 +300,8 @@ class Toaster(Component):
295
300
  """
296
301
  return Toaster.send_toast(message, level="success", **kwargs)
297
302
 
298
- def toast_dismiss(self, id: str | None):
303
+ @staticmethod
304
+ def toast_dismiss(id: Var | str | None = None):
299
305
  """Dismiss a toast.
300
306
 
301
307
  Args:
@@ -304,12 +310,22 @@ class Toaster(Component):
304
310
  Returns:
305
311
  The toast dismiss event.
306
312
  """
307
- if id is None:
308
- dismiss = f"{toast_ref}.dismiss()"
313
+ dismiss_var_data = None
314
+
315
+ if isinstance(id, Var):
316
+ dismiss = f"{toast_ref}.dismiss({id._var_name_unwrapped})"
317
+ dismiss_var_data = id._var_data
318
+ elif isinstance(id, str):
319
+ dismiss = f"{toast_ref}.dismiss('{id}')"
309
320
  else:
310
- dismiss = f"{toast_ref}.dismiss({id})"
311
- dismiss_action = Var.create(dismiss, _var_is_string=False, _var_is_local=True)
312
- return call_script(dismiss_action) # type: ignore
321
+ dismiss = f"{toast_ref}.dismiss()"
322
+ dismiss_action = Var.create_safe(
323
+ dismiss,
324
+ _var_is_string=False,
325
+ _var_is_local=True,
326
+ _var_data=dismiss_var_data,
327
+ )
328
+ return call_script(dismiss_action)
313
329
 
314
330
 
315
331
  # TODO: figure out why loading toast stay open forever
@@ -46,7 +46,7 @@ class ToastProps(PropsBase):
46
46
  dismissible: Optional[bool]
47
47
  action: Optional[ToastAction]
48
48
  cancel: Optional[ToastAction]
49
- id: Optional[str]
49
+ id: Optional[Union[str, Var]]
50
50
  unstyled: Optional[bool]
51
51
  style: Optional[Style]
52
52
  action_button_styles: Optional[Style]
@@ -54,9 +54,10 @@ class ToastProps(PropsBase):
54
54
  on_dismiss: Optional[Any]
55
55
  on_auto_close: Optional[Any]
56
56
 
57
- def dict(self, *args, **kwargs) -> dict: ...
57
+ def dict(self, *args, **kwargs) -> dict[str, Any]: ...
58
58
 
59
59
  class Toaster(Component):
60
+ def add_hooks(self) -> list[Var | str]: ...
60
61
  @staticmethod
61
62
  def send_toast(message: str, level: str | None = None, **props) -> EventSpec: ...
62
63
  @staticmethod
@@ -67,7 +68,8 @@ class Toaster(Component):
67
68
  def toast_error(message: str, **kwargs): ...
68
69
  @staticmethod
69
70
  def toast_success(message: str, **kwargs): ...
70
- def toast_dismiss(self, id: str | None): ...
71
+ @staticmethod
72
+ def toast_dismiss(id: Var | str | None = None): ...
71
73
  @overload
72
74
  @classmethod
73
75
  def create( # type: ignore
@@ -202,7 +204,9 @@ class ToastNamespace(ComponentNamespace):
202
204
  dismiss = staticmethod(Toaster.toast_dismiss)
203
205
 
204
206
  @staticmethod
205
- def __call__(message: str, level: Optional[str], **props) -> "Optional[EventSpec]":
207
+ def __call__(
208
+ message: str, level: Optional[str] = None, **props
209
+ ) -> "Optional[EventSpec]":
206
210
  """Send a toast message.
207
211
 
208
212
  Args:
reflex/config.py CHANGED
@@ -161,13 +161,13 @@ class Config(Base):
161
161
  loglevel: constants.LogLevel = constants.LogLevel.INFO
162
162
 
163
163
  # The port to run the frontend on. NOTE: When running in dev mode, the next available port will be used if this is taken.
164
- frontend_port: int = 3000
164
+ frontend_port: int = constants.DefaultPorts.FRONTEND_PORT
165
165
 
166
166
  # The path to run the frontend on. For example, "/app" will run the frontend on http://localhost:3000/app
167
167
  frontend_path: str = ""
168
168
 
169
169
  # The port to run the backend on. NOTE: When running in dev mode, the next available port will be used if this is taken.
170
- backend_port: int = 8000
170
+ backend_port: int = constants.DefaultPorts.BACKEND_PORT
171
171
 
172
172
  # The backend url the frontend will connect to. This must be updated if the backend is hosted elsewhere, or in production.
173
173
  api_url: str = f"http://localhost:{backend_port}"
@@ -316,17 +316,22 @@ class Config(Base):
316
316
  ):
317
317
  self.deploy_url = f"http://localhost:{kwargs['frontend_port']}"
318
318
 
319
- # If running in Github Codespaces, override API_URL
320
- codespace_name = os.getenv("CODESPACE_NAME")
321
- if "api_url" not in self._non_default_attributes and codespace_name:
319
+ if "api_url" not in self._non_default_attributes:
320
+ # If running in Github Codespaces, override API_URL
321
+ codespace_name = os.getenv("CODESPACE_NAME")
322
322
  GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN = os.getenv(
323
323
  "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"
324
324
  )
325
- if codespace_name:
325
+ # If running on Replit.com interactively, override API_URL to ensure we maintain the backend_port
326
+ replit_dev_domain = os.getenv("REPLIT_DEV_DOMAIN")
327
+ backend_port = kwargs.get("backend_port", self.backend_port)
328
+ if codespace_name and GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:
326
329
  self.api_url = (
327
330
  f"https://{codespace_name}-{kwargs.get('backend_port', self.backend_port)}"
328
331
  f".{GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}"
329
332
  )
333
+ elif replit_dev_domain and backend_port:
334
+ self.api_url = f"https://{replit_dev_domain}:{backend_port}"
330
335
 
331
336
  def _set_persistent(self, **kwargs):
332
337
  """Set values in this config and in the environment so they persist into subprocess.
@@ -352,11 +357,14 @@ def get_config(reload: bool = False) -> Config:
352
357
  The app config.
353
358
  """
354
359
  sys.path.insert(0, os.getcwd())
355
- try:
356
- rxconfig = __import__(constants.Config.MODULE)
357
- if reload:
358
- importlib.reload(rxconfig)
359
- return rxconfig.config
360
-
361
- except ImportError:
362
- return Config(app_name="") # type: ignore
360
+ # only import the module if it exists. If a module spec exists then
361
+ # the module exists.
362
+ spec = importlib.util.find_spec(constants.Config.MODULE) # type: ignore
363
+ if not spec:
364
+ # we need this condition to ensure that a ModuleNotFound error is not thrown when
365
+ # running unit/integration tests.
366
+ return Config(app_name="")
367
+ rxconfig = importlib.import_module(constants.Config.MODULE)
368
+ if reload:
369
+ importlib.reload(rxconfig)
370
+ return rxconfig.config
@@ -36,6 +36,7 @@ from .compiler import (
36
36
  from .config import (
37
37
  ALEMBIC_CONFIG,
38
38
  Config,
39
+ DefaultPorts,
39
40
  Expiration,
40
41
  GitIgnore,
41
42
  RequirementsTxt,
@@ -72,6 +73,7 @@ __ALL__ = [
72
73
  ComponentName,
73
74
  CustomComponents,
74
75
  DefaultPage,
76
+ DefaultPorts,
75
77
  Dirs,
76
78
  Endpoint,
77
79
  Env,
@@ -50,5 +50,12 @@ class RequirementsTxt(SimpleNamespace):
50
50
  DEFAULTS_STUB = f"{Reflex.MODULE_NAME}=="
51
51
 
52
52
 
53
+ class DefaultPorts(SimpleNamespace):
54
+ """Default port constants."""
55
+
56
+ FRONTEND_PORT = 3000
57
+ BACKEND_PORT = 8000
58
+
59
+
53
60
  # The deployment URL.
54
61
  PRODUCTION_BACKEND_URL = "https://{username}-{app_name}.api.pynecone.app"
reflex/event.py CHANGED
@@ -415,6 +415,8 @@ class FileUpload(Base):
415
415
  ) # type: ignore
416
416
  else:
417
417
  raise ValueError(f"{on_upload_progress} is not a valid event handler.")
418
+ if isinstance(events, Var):
419
+ raise ValueError(f"{on_upload_progress} cannot return a var {events}.")
418
420
  on_upload_progress_chain = EventChain(
419
421
  events=events,
420
422
  args_spec=self.on_upload_progress_args_spec,
@@ -714,7 +716,7 @@ def _callback_arg_spec(eval_result):
714
716
 
715
717
 
716
718
  def call_script(
717
- javascript_code: str,
719
+ javascript_code: str | Var[str],
718
720
  callback: EventSpec
719
721
  | EventHandler
720
722
  | Callable
@@ -831,19 +833,19 @@ def parse_args_spec(arg_spec: ArgsSpec):
831
833
  )
832
834
 
833
835
 
834
- def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
836
+ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec] | Var:
835
837
  """Call a function to a list of event specs.
836
838
 
837
- The function should return either a single EventSpec or a list of EventSpecs.
838
- If the function takes in an arg, the arg will be passed to the function.
839
- Otherwise, the function will be called with no args.
839
+ The function should return a single EventSpec, a list of EventSpecs, or a
840
+ single Var. If the function takes in an arg, the arg will be passed to the
841
+ function. Otherwise, the function will be called with no args.
840
842
 
841
843
  Args:
842
844
  fn: The function to call.
843
845
  arg: The argument to pass to the function.
844
846
 
845
847
  Returns:
846
- The event specs from calling the function.
848
+ The event specs from calling the function or a Var.
847
849
 
848
850
  Raises:
849
851
  EventHandlerValueError: If the lambda has an invalid signature.
@@ -866,6 +868,10 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
866
868
  else:
867
869
  raise EventHandlerValueError(f"Lambda {fn} must have 0 or 1 arguments.")
868
870
 
871
+ # If the function returns a Var, assume it's an EventChain and render it directly.
872
+ if isinstance(out, Var):
873
+ return out
874
+
869
875
  # Convert the output to a list.
870
876
  if not isinstance(out, List):
871
877
  out = [out]
@@ -17,7 +17,28 @@ warn(
17
17
  "`rx._x` contains experimental features and might be removed at any time in the future .",
18
18
  )
19
19
 
20
- _x = SimpleNamespace(
20
+ _EMITTED_PROMOTION_WARNINGS = set()
21
+
22
+
23
+ class ExperimentalNamespace(SimpleNamespace):
24
+ """Namespace for experimental features."""
25
+
26
+ @property
27
+ def toast(self):
28
+ """Temporary property returning the toast namespace.
29
+
30
+ Remove this property when toast is fully promoted.
31
+
32
+ Returns:
33
+ The toast namespace.
34
+ """
35
+ if "toast" not in _EMITTED_PROMOTION_WARNINGS:
36
+ _EMITTED_PROMOTION_WARNINGS.add("toast")
37
+ warn(f"`rx._x.toast` was promoted to `rx.toast`.")
38
+ return toast
39
+
40
+
41
+ _x = ExperimentalNamespace(
21
42
  asset=asset,
22
43
  client_state=ClientStateVar.create,
23
44
  hooks=hooks,
@@ -25,5 +46,4 @@ _x = SimpleNamespace(
25
46
  progress=progress,
26
47
  PropsBase=PropsBase,
27
48
  run_in_thread=run_in_thread,
28
- toast=toast,
29
49
  )