reflex 0.5.0a3__py3-none-any.whl → 0.5.1__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 (42) hide show
  1. reflex/.templates/web/utils/state.js +7 -2
  2. reflex/app.py +68 -50
  3. reflex/app_module_for_backend.py +3 -0
  4. reflex/base.py +5 -2
  5. reflex/components/component.py +49 -13
  6. reflex/components/core/__init__.py +7 -1
  7. reflex/components/core/banner.py +79 -6
  8. reflex/components/core/banner.pyi +130 -0
  9. reflex/components/core/cond.py +10 -4
  10. reflex/components/core/debounce.py +2 -4
  11. reflex/components/core/foreach.py +11 -0
  12. reflex/components/core/upload.py +9 -10
  13. reflex/components/el/elements/forms.py +12 -6
  14. reflex/components/el/elements/media.py +19 -0
  15. reflex/components/el/elements/media.pyi +3 -1
  16. reflex/components/gridjs/datatable.py +4 -2
  17. reflex/components/props.py +30 -0
  18. reflex/components/radix/themes/components/tabs.py +1 -1
  19. reflex/components/sonner/toast.py +102 -35
  20. reflex/components/sonner/toast.pyi +27 -14
  21. reflex/config.py +5 -3
  22. reflex/constants/compiler.py +3 -3
  23. reflex/constants/installer.py +1 -1
  24. reflex/event.py +38 -24
  25. reflex/experimental/__init__.py +4 -0
  26. reflex/experimental/client_state.py +198 -0
  27. reflex/state.py +59 -21
  28. reflex/style.py +3 -3
  29. reflex/testing.py +28 -9
  30. reflex/utils/exceptions.py +64 -8
  31. reflex/utils/format.py +73 -2
  32. reflex/utils/prerequisites.py +68 -23
  33. reflex/utils/processes.py +34 -4
  34. reflex/utils/telemetry.py +42 -16
  35. reflex/utils/types.py +16 -0
  36. reflex/vars.py +104 -61
  37. reflex/vars.pyi +7 -6
  38. {reflex-0.5.0a3.dist-info → reflex-0.5.1.dist-info}/METADATA +1 -1
  39. {reflex-0.5.0a3.dist-info → reflex-0.5.1.dist-info}/RECORD +42 -40
  40. {reflex-0.5.0a3.dist-info → reflex-0.5.1.dist-info}/LICENSE +0 -0
  41. {reflex-0.5.0a3.dist-info → reflex-0.5.1.dist-info}/WHEEL +0 -0
  42. {reflex-0.5.0a3.dist-info → reflex-0.5.1.dist-info}/entry_points.txt +0 -0
@@ -7,15 +7,16 @@ from typing import Any, Dict, Literal, Optional, Union, overload
7
7
  from reflex.vars import Var, BaseVar, ComputedVar
8
8
  from reflex.event import EventChain, EventHandler, EventSpec
9
9
  from reflex.style import Style
10
- from typing import Literal
10
+ from typing import Any, Literal, Optional, Union
11
11
  from reflex.base import Base
12
12
  from reflex.components.component import Component, ComponentNamespace
13
13
  from reflex.components.lucide.icon import Icon
14
+ from reflex.components.props import PropsBase
14
15
  from reflex.event import EventSpec, call_script
15
16
  from reflex.style import Style, color_mode
16
17
  from reflex.utils import format
17
18
  from reflex.utils.imports import ImportVar
18
- from reflex.utils.serializers import serialize
19
+ from reflex.utils.serializers import serialize, serializer
19
20
  from reflex.vars import Var, VarData
20
21
 
21
22
  LiteralPosition = Literal[
@@ -28,20 +29,32 @@ LiteralPosition = Literal[
28
29
  ]
29
30
  toast_ref = Var.create_safe("refs['__toast']")
30
31
 
31
- class PropsBase(Base):
32
- def json(self) -> str: ...
32
+ class ToastAction(Base):
33
+ label: str
34
+ on_click: Any
35
+
36
+ @serializer
37
+ def serialize_action(action: ToastAction) -> dict: ...
33
38
 
34
39
  class ToastProps(PropsBase):
35
- description: str
36
- close_button: bool
37
- invert: bool
38
- important: bool
39
- duration: int
40
- position: LiteralPosition
41
- dismissible: bool
42
- id: str
43
- unstyled: bool
44
- style: Style
40
+ description: Optional[Union[str, Var]]
41
+ close_button: Optional[bool]
42
+ invert: Optional[bool]
43
+ important: Optional[bool]
44
+ duration: Optional[int]
45
+ position: Optional[LiteralPosition]
46
+ dismissible: Optional[bool]
47
+ action: Optional[ToastAction]
48
+ cancel: Optional[ToastAction]
49
+ id: Optional[str]
50
+ unstyled: Optional[bool]
51
+ style: Optional[Style]
52
+ action_button_styles: Optional[Style]
53
+ cancel_button_styles: Optional[Style]
54
+ on_dismiss: Optional[Any]
55
+ on_auto_close: Optional[Any]
56
+
57
+ def dict(self, *args, **kwargs) -> dict: ...
45
58
 
46
59
  class Toaster(Component):
47
60
  @staticmethod
reflex/config.py CHANGED
@@ -251,8 +251,10 @@ class Config(Base):
251
251
  The updated config values.
252
252
 
253
253
  Raises:
254
- ValueError: If an environment variable is set to an invalid type.
254
+ EnvVarValueError: If an environment variable is set to an invalid type.
255
255
  """
256
+ from reflex.utils.exceptions import EnvVarValueError
257
+
256
258
  updated_values = {}
257
259
  # Iterate over the fields.
258
260
  for key, field in self.__fields__.items():
@@ -273,11 +275,11 @@ class Config(Base):
273
275
  env_var = env_var.lower() in ["true", "1", "yes"]
274
276
  else:
275
277
  env_var = field.type_(env_var)
276
- except ValueError:
278
+ except ValueError as ve:
277
279
  console.error(
278
280
  f"Could not convert {key.upper()}={env_var} to type {field.type_}"
279
281
  )
280
- raise
282
+ raise EnvVarValueError from ve
281
283
 
282
284
  # Set the value.
283
285
  updated_values[key] = env_var
@@ -103,9 +103,9 @@ class Imports(SimpleNamespace):
103
103
  """Common sets of import vars."""
104
104
 
105
105
  EVENTS = {
106
- "react": {ImportVar(tag="useContext")},
107
- f"/{Dirs.CONTEXTS_PATH}": {ImportVar(tag="EventLoopContext")},
108
- f"/{Dirs.STATE_PATH}": {ImportVar(tag=CompileVars.TO_EVENT)},
106
+ "react": [ImportVar(tag="useContext")],
107
+ f"/{Dirs.CONTEXTS_PATH}": [ImportVar(tag="EventLoopContext")],
108
+ f"/{Dirs.STATE_PATH}": [ImportVar(tag=CompileVars.TO_EVENT)],
109
109
  }
110
110
 
111
111
 
@@ -35,7 +35,7 @@ class Bun(SimpleNamespace):
35
35
  """Bun constants."""
36
36
 
37
37
  # The Bun version.
38
- VERSION = "1.1.6"
38
+ VERSION = "1.1.8"
39
39
  # Min Bun Version
40
40
  MIN_VERSION = "0.7.0"
41
41
  # The directory to store the bun.
reflex/event.py CHANGED
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import inspect
6
6
  from base64 import b64encode
7
- from types import FunctionType
8
7
  from typing import (
9
8
  Any,
10
9
  Callable,
@@ -180,8 +179,10 @@ class EventHandler(EventActionsMixin):
180
179
  The event spec, containing both the function and args.
181
180
 
182
181
  Raises:
183
- TypeError: If the arguments are invalid.
182
+ EventHandlerTypeError: If the arguments are invalid.
184
183
  """
184
+ from reflex.utils.exceptions import EventHandlerTypeError
185
+
185
186
  # Get the function args.
186
187
  fn_args = inspect.getfullargspec(self.fn).args[1:]
187
188
  fn_args = (Var.create_safe(arg) for arg in fn_args)
@@ -197,7 +198,7 @@ class EventHandler(EventActionsMixin):
197
198
  try:
198
199
  values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
199
200
  except TypeError as e:
200
- raise TypeError(
201
+ raise EventHandlerTypeError(
201
202
  f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
202
203
  ) from e
203
204
  payload = tuple(zip(fn_args, values))
@@ -256,8 +257,10 @@ class EventSpec(EventActionsMixin):
256
257
  The event spec with the new arguments.
257
258
 
258
259
  Raises:
259
- TypeError: If the arguments are invalid.
260
+ EventHandlerTypeError: If the arguments are invalid.
260
261
  """
262
+ from reflex.utils.exceptions import EventHandlerTypeError
263
+
261
264
  # Get the remaining unfilled function args.
262
265
  fn_args = inspect.getfullargspec(self.handler.fn).args[1 + len(self.args) :]
263
266
  fn_args = (Var.create_safe(arg) for arg in fn_args)
@@ -268,7 +271,7 @@ class EventSpec(EventActionsMixin):
268
271
  try:
269
272
  values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
270
273
  except TypeError as e:
271
- raise TypeError(
274
+ raise EventHandlerTypeError(
272
275
  f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
273
276
  ) from e
274
277
  new_payload = tuple(zip(fn_args, values))
@@ -312,10 +315,12 @@ class CallableEventSpec(EventSpec):
312
315
  The EventSpec returned from calling the function.
313
316
 
314
317
  Raises:
315
- TypeError: If the CallableEventSpec has no associated function.
318
+ EventHandlerTypeError: If the CallableEventSpec has no associated function.
316
319
  """
320
+ from reflex.utils.exceptions import EventHandlerTypeError
321
+
317
322
  if self.fn is None:
318
- raise TypeError("CallableEventSpec has no associated function.")
323
+ raise EventHandlerTypeError("CallableEventSpec has no associated function.")
319
324
  return self.fn(*args, **kwargs)
320
325
 
321
326
 
@@ -462,18 +467,27 @@ def server_side(name: str, sig: inspect.Signature, **kwargs) -> EventSpec:
462
467
  )
463
468
 
464
469
 
465
- def redirect(path: str | Var[str], external: Optional[bool] = False) -> EventSpec:
470
+ def redirect(
471
+ path: str | Var[str],
472
+ external: Optional[bool] = False,
473
+ replace: Optional[bool] = False,
474
+ ) -> EventSpec:
466
475
  """Redirect to a new path.
467
476
 
468
477
  Args:
469
478
  path: The path to redirect to.
470
479
  external: Whether to open in new tab or not.
480
+ replace: If True, the current page will not create a new history entry.
471
481
 
472
482
  Returns:
473
483
  An event to redirect to the path.
474
484
  """
475
485
  return server_side(
476
- "_redirect", get_fn_signature(redirect), path=path, external=external
486
+ "_redirect",
487
+ get_fn_signature(redirect),
488
+ path=path,
489
+ external=external,
490
+ replace=replace,
477
491
  )
478
492
 
479
493
 
@@ -700,7 +714,11 @@ def _callback_arg_spec(eval_result):
700
714
 
701
715
  def call_script(
702
716
  javascript_code: str,
703
- callback: EventHandler | Callable | None = None,
717
+ callback: EventSpec
718
+ | EventHandler
719
+ | Callable
720
+ | List[EventSpec | EventHandler | Callable]
721
+ | None = None,
704
722
  ) -> EventSpec:
705
723
  """Create an event handler that executes arbitrary javascript code.
706
724
 
@@ -710,21 +728,14 @@ def call_script(
710
728
 
711
729
  Returns:
712
730
  EventSpec: An event that will execute the client side javascript.
713
-
714
- Raises:
715
- ValueError: If the callback is not a valid event handler.
716
731
  """
717
732
  callback_kwargs = {}
718
733
  if callback is not None:
719
- arg_name = parse_args_spec(_callback_arg_spec)[0]._var_name
720
- if isinstance(callback, EventHandler):
721
- event_spec = call_event_handler(callback, _callback_arg_spec)
722
- elif isinstance(callback, FunctionType):
723
- event_spec = call_event_fn(callback, _callback_arg_spec)[0]
724
- else:
725
- raise ValueError("Cannot use {callback!r} as a call_script callback.")
726
734
  callback_kwargs = {
727
- "callback": f"({arg_name}) => queueEvents([{format.format_event(event_spec)}], {constants.CompileVars.SOCKET})"
735
+ "callback": format.format_queue_events(
736
+ callback,
737
+ args_spec=lambda result: [result],
738
+ )
728
739
  }
729
740
  return server_side(
730
741
  "_call_script",
@@ -834,10 +845,11 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
834
845
  The event specs from calling the function.
835
846
 
836
847
  Raises:
837
- ValueError: If the lambda has an invalid signature.
848
+ EventHandlerValueError: If the lambda has an invalid signature.
838
849
  """
839
850
  # Import here to avoid circular imports.
840
851
  from reflex.event import EventHandler, EventSpec
852
+ from reflex.utils.exceptions import EventHandlerValueError
841
853
 
842
854
  # Get the args of the lambda.
843
855
  args = inspect.getfullargspec(fn).args
@@ -851,7 +863,7 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
851
863
  elif len(args) == 1:
852
864
  out = fn(arg)
853
865
  else:
854
- raise ValueError(f"Lambda {fn} must have 0 or 1 arguments.")
866
+ raise EventHandlerValueError(f"Lambda {fn} must have 0 or 1 arguments.")
855
867
 
856
868
  # Convert the output to a list.
857
869
  if not isinstance(out, List):
@@ -869,7 +881,9 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
869
881
 
870
882
  # Make sure the event spec is valid.
871
883
  if not isinstance(e, EventSpec):
872
- raise ValueError(f"Lambda {fn} returned an invalid event spec: {e}.")
884
+ raise EventHandlerValueError(
885
+ f"Lambda {fn} returned an invalid event spec: {e}."
886
+ )
873
887
 
874
888
  # Add the event spec to the chain.
875
889
  events.append(e)
@@ -2,11 +2,13 @@
2
2
 
3
3
  from types import SimpleNamespace
4
4
 
5
+ from reflex.components.props import PropsBase
5
6
  from reflex.components.radix.themes.components.progress import progress as progress
6
7
  from reflex.components.sonner.toast import toast as toast
7
8
 
8
9
  from ..utils.console import warn
9
10
  from . import hooks as hooks
11
+ from .client_state import ClientStateVar as ClientStateVar
10
12
  from .layout import layout as layout
11
13
  from .misc import run_in_thread as run_in_thread
12
14
 
@@ -15,9 +17,11 @@ warn(
15
17
  )
16
18
 
17
19
  _x = SimpleNamespace(
20
+ client_state=ClientStateVar.create,
18
21
  hooks=hooks,
19
22
  layout=layout,
20
23
  progress=progress,
24
+ PropsBase=PropsBase,
21
25
  run_in_thread=run_in_thread,
22
26
  toast=toast,
23
27
  )
@@ -0,0 +1,198 @@
1
+ """Handle client side state with `useState`."""
2
+
3
+ import dataclasses
4
+ import sys
5
+ from typing import Any, Callable, Optional, Type
6
+
7
+ from reflex import constants
8
+ from reflex.event import EventChain, EventHandler, EventSpec, call_script
9
+ from reflex.utils.imports import ImportVar
10
+ from reflex.vars import Var, VarData
11
+
12
+
13
+ def _client_state_ref(var_name: str) -> str:
14
+ """Get the ref path for a ClientStateVar.
15
+
16
+ Args:
17
+ var_name: The name of the variable.
18
+
19
+ Returns:
20
+ An accessor for ClientStateVar ref as a string.
21
+ """
22
+ return f"refs['_client_state_{var_name}']"
23
+
24
+
25
+ @dataclasses.dataclass(
26
+ eq=False,
27
+ **{"slots": True} if sys.version_info >= (3, 10) else {},
28
+ )
29
+ class ClientStateVar(Var):
30
+ """A Var that exists on the client via useState."""
31
+
32
+ # The name of the var.
33
+ _var_name: str = dataclasses.field()
34
+
35
+ # Track the names of the getters and setters
36
+ _setter_name: str = dataclasses.field()
37
+ _getter_name: str = dataclasses.field()
38
+
39
+ # The type of the var.
40
+ _var_type: Type = dataclasses.field(default=Any)
41
+
42
+ # Whether this is a local javascript variable.
43
+ _var_is_local: bool = dataclasses.field(default=False)
44
+
45
+ # Whether the var is a string literal.
46
+ _var_is_string: bool = dataclasses.field(default=False)
47
+
48
+ # _var_full_name should be prefixed with _var_state
49
+ _var_full_name_needs_state_prefix: bool = dataclasses.field(default=False)
50
+
51
+ # Extra metadata associated with the Var
52
+ _var_data: Optional[VarData] = dataclasses.field(default=None)
53
+
54
+ def __hash__(self) -> int:
55
+ """Define a hash function for a var.
56
+
57
+ Returns:
58
+ The hash of the var.
59
+ """
60
+ return hash(
61
+ (self._var_name, str(self._var_type), self._getter_name, self._setter_name)
62
+ )
63
+
64
+ @classmethod
65
+ def create(cls, var_name, default=None) -> "ClientStateVar":
66
+ """Create a local_state Var that can be accessed and updated on the client.
67
+
68
+ The `ClientStateVar` should be included in the highest parent component
69
+ that contains the components which will access and manipulate the client
70
+ state. It has no visual rendering, including it ensures that the
71
+ `useState` hook is called in the correct scope.
72
+
73
+ To render the var in a component, use the `value` property.
74
+
75
+ To update the var in a component, use the `set` property.
76
+
77
+ To access the var in an event handler, use the `retrieve` method with
78
+ `callback` set to the event handler which should receive the value.
79
+
80
+ To update the var in an event handler, use the `push` method with the
81
+ value to update.
82
+
83
+ Args:
84
+ var_name: The name of the variable.
85
+ default: The default value of the variable.
86
+
87
+ Returns:
88
+ ClientStateVar
89
+ """
90
+ if default is None:
91
+ default_var = Var.create_safe("", _var_is_local=False, _var_is_string=False)
92
+ elif not isinstance(default, Var):
93
+ default_var = Var.create_safe(default)
94
+ else:
95
+ default_var = default
96
+ setter_name = f"set{var_name.capitalize()}"
97
+ return cls(
98
+ _var_name="",
99
+ _setter_name=setter_name,
100
+ _getter_name=var_name,
101
+ _var_is_local=False,
102
+ _var_is_string=False,
103
+ _var_type=default_var._var_type,
104
+ _var_data=VarData.merge(
105
+ default_var._var_data,
106
+ VarData( # type: ignore
107
+ hooks={
108
+ f"const [{var_name}, {setter_name}] = useState({default_var._var_name_unwrapped})": None,
109
+ f"{_client_state_ref(var_name)} = {var_name}": None,
110
+ f"{_client_state_ref(setter_name)} = {setter_name}": None,
111
+ },
112
+ imports={
113
+ "react": [ImportVar(tag="useState", install=False)],
114
+ f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
115
+ },
116
+ ),
117
+ ),
118
+ )
119
+
120
+ @property
121
+ def value(self) -> Var:
122
+ """Get a placeholder for the Var.
123
+
124
+ This property can only be rendered on the frontend.
125
+
126
+ To access the value in a backend event handler, see `retrieve`.
127
+
128
+ Returns:
129
+ an accessor for the client state variable.
130
+ """
131
+ return (
132
+ Var.create_safe(
133
+ _client_state_ref(self._getter_name),
134
+ _var_is_local=False,
135
+ _var_is_string=False,
136
+ )
137
+ .to(self._var_type)
138
+ ._replace(
139
+ merge_var_data=VarData( # type: ignore
140
+ imports={
141
+ f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
142
+ }
143
+ )
144
+ )
145
+ )
146
+
147
+ @property
148
+ def set(self) -> Var:
149
+ """Set the value of the client state variable.
150
+
151
+ This property can only be attached to a frontend event trigger.
152
+
153
+ To set a value from a backend event handler, see `push`.
154
+
155
+ Returns:
156
+ A special EventChain Var which will set the value when triggered.
157
+ """
158
+ return (
159
+ Var.create_safe(
160
+ _client_state_ref(self._setter_name),
161
+ _var_is_local=False,
162
+ _var_is_string=False,
163
+ )
164
+ .to(EventChain)
165
+ ._replace(
166
+ merge_var_data=VarData( # type: ignore
167
+ imports={
168
+ f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
169
+ }
170
+ )
171
+ )
172
+ )
173
+
174
+ def retrieve(self, callback: EventHandler | Callable | None = None) -> EventSpec:
175
+ """Pass the value of the client state variable to a backend EventHandler.
176
+
177
+ The event handler must `yield` or `return` the EventSpec to trigger the event.
178
+
179
+ Args:
180
+ callback: The callback to pass the value to.
181
+
182
+ Returns:
183
+ An EventSpec which will retrieve the value when triggered.
184
+ """
185
+ return call_script(_client_state_ref(self._getter_name), callback=callback)
186
+
187
+ def push(self, value: Any) -> EventSpec:
188
+ """Push a value to the client state variable from the backend.
189
+
190
+ The event handler must `yield` or `return` the EventSpec to trigger the event.
191
+
192
+ Args:
193
+ value: The value to update.
194
+
195
+ Returns:
196
+ An EventSpec which will push the value when triggered.
197
+ """
198
+ return call_script(f"{_client_state_ref(self._setter_name)}({value})")