reflex 0.5.3a1__py3-none-any.whl → 0.5.4__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 (75) 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 +3 -3
  8. reflex/components/chakra/base.py +3 -1
  9. reflex/components/chakra/forms/checkbox.py +1 -1
  10. reflex/components/chakra/forms/pininput.py +1 -1
  11. reflex/components/chakra/forms/rangeslider.py +1 -1
  12. reflex/components/chakra/navigation/link.py +3 -1
  13. reflex/components/component.py +43 -15
  14. reflex/components/core/banner.py +2 -1
  15. reflex/components/core/client_side_routing.py +3 -3
  16. reflex/components/core/client_side_routing.pyi +1 -0
  17. reflex/components/core/debounce.py +3 -1
  18. reflex/components/core/foreach.py +1 -1
  19. reflex/components/core/html.py +1 -1
  20. reflex/components/core/upload.py +2 -1
  21. reflex/components/datadisplay/code.py +4 -1
  22. reflex/components/datadisplay/dataeditor.py +11 -8
  23. reflex/components/datadisplay/dataeditor.pyi +1 -0
  24. reflex/components/el/__init__.pyi +2 -1
  25. reflex/components/el/elements/__init__.py +1 -1
  26. reflex/components/el/elements/__init__.pyi +3 -2
  27. reflex/components/el/elements/forms.py +25 -14
  28. reflex/components/el/elements/forms.pyi +2 -1
  29. reflex/components/markdown/markdown.py +17 -11
  30. reflex/components/markdown/markdown.pyi +12 -8
  31. reflex/components/plotly/plotly.py +83 -15
  32. reflex/components/plotly/plotly.pyi +15 -82
  33. reflex/components/radix/primitives/accordion.py +1 -1
  34. reflex/components/radix/themes/base.py +10 -2
  35. reflex/components/radix/themes/base.pyi +1 -0
  36. reflex/components/radix/themes/color_mode.py +1 -1
  37. reflex/components/radix/themes/components/checkbox.py +3 -1
  38. reflex/components/radix/themes/components/radio_group.py +6 -4
  39. reflex/components/radix/themes/components/separator.py +1 -1
  40. reflex/components/radix/themes/layout/container.py +1 -1
  41. reflex/components/radix/themes/layout/section.py +1 -1
  42. reflex/components/recharts/cartesian.py +42 -14
  43. reflex/components/recharts/cartesian.pyi +81 -17
  44. reflex/components/recharts/charts.py +12 -21
  45. reflex/components/recharts/charts.pyi +53 -14
  46. reflex/components/sonner/toast.py +37 -17
  47. reflex/components/sonner/toast.pyi +9 -5
  48. reflex/components/tags/tag.py +2 -1
  49. reflex/config.py +22 -14
  50. reflex/constants/__init__.py +2 -0
  51. reflex/constants/base.py +3 -1
  52. reflex/constants/config.py +7 -0
  53. reflex/event.py +24 -15
  54. reflex/experimental/__init__.py +22 -2
  55. reflex/experimental/client_state.py +81 -23
  56. reflex/experimental/hooks.py +35 -35
  57. reflex/experimental/layout.py +17 -5
  58. reflex/experimental/layout.pyi +536 -0
  59. reflex/reflex.py +9 -5
  60. reflex/style.py +3 -2
  61. reflex/testing.py +44 -13
  62. reflex/utils/compat.py +5 -0
  63. reflex/utils/format.py +20 -6
  64. reflex/utils/processes.py +27 -0
  65. reflex/utils/pyi_generator.py +11 -4
  66. reflex/utils/serializers.py +120 -16
  67. reflex/utils/types.py +9 -3
  68. reflex/vars.py +70 -21
  69. reflex/vars.pyi +2 -2
  70. {reflex-0.5.3a1.dist-info → reflex-0.5.4.dist-info}/METADATA +1 -1
  71. {reflex-0.5.3a1.dist-info → reflex-0.5.4.dist-info}/RECORD +74 -74
  72. reflex/constants/base.pyi +0 -99
  73. {reflex-0.5.3a1.dist-info → reflex-0.5.4.dist-info}/LICENSE +0 -0
  74. {reflex-0.5.3a1.dist-info → reflex-0.5.4.dist-info}/WHEEL +0 -0
  75. {reflex-0.5.3a1.dist-info → reflex-0.5.4.dist-info}/entry_points.txt +0 -0
reflex/utils/compat.py CHANGED
@@ -21,6 +21,11 @@ def pydantic_v1_patch():
21
21
  try:
22
22
  import pydantic.v1 # type: ignore
23
23
 
24
+ if pydantic.__version__.startswith("1."):
25
+ # pydantic v1 is already installed
26
+ yield
27
+ return
28
+
24
29
  sys.modules["pydantic.fields"] = pydantic.v1.fields # type: ignore
25
30
  sys.modules["pydantic.main"] = pydantic.v1.main # type: ignore
26
31
  sys.modules["pydantic.errors"] = pydantic.v1.errors # type: ignore
reflex/utils/format.py CHANGED
@@ -9,8 +9,7 @@ import re
9
9
  from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
10
10
 
11
11
  from reflex import constants
12
- from reflex.utils import exceptions, serializers, types
13
- from reflex.utils.serializers import serialize
12
+ from reflex.utils import exceptions, types
14
13
  from reflex.vars import BaseVar, Var
15
14
 
16
15
  if TYPE_CHECKING:
@@ -400,6 +399,7 @@ def format_prop(
400
399
  """
401
400
  # import here to avoid circular import.
402
401
  from reflex.event import EventChain
402
+ from reflex.utils import serializers
403
403
 
404
404
  try:
405
405
  # Handle var props.
@@ -612,6 +612,9 @@ def format_queue_events(
612
612
 
613
613
  Returns:
614
614
  The compiled javascript callback to queue the given events on the frontend.
615
+
616
+ Raises:
617
+ ValueError: If a lambda function is given which returns a Var.
615
618
  """
616
619
  from reflex.event import (
617
620
  EventChain,
@@ -648,7 +651,11 @@ def format_queue_events(
648
651
  if isinstance(spec, (EventHandler, EventSpec)):
649
652
  specs = [call_event_handler(spec, args_spec or _default_args_spec)]
650
653
  elif isinstance(spec, type(lambda: None)):
651
- specs = call_event_fn(spec, args_spec or _default_args_spec)
654
+ specs = call_event_fn(spec, args_spec or _default_args_spec) # type: ignore
655
+ if isinstance(specs, Var):
656
+ raise ValueError(
657
+ f"Invalid event spec: {specs}. Expected a list of EventSpecs."
658
+ )
652
659
  payloads.extend(format_event(s) for s in specs)
653
660
 
654
661
  # Return the final code snippet, expecting queueEvents, processEvent, and socket to be in scope.
@@ -687,6 +694,8 @@ def format_state(value: Any, key: Optional[str] = None) -> Any:
687
694
  Raises:
688
695
  TypeError: If the given value is not a valid state.
689
696
  """
697
+ from reflex.utils import serializers
698
+
690
699
  # Handle dicts.
691
700
  if isinstance(value, dict):
692
701
  return {k: format_state(v, k) for k, v in value.items()}
@@ -700,7 +709,7 @@ def format_state(value: Any, key: Optional[str] = None) -> Any:
700
709
  return value
701
710
 
702
711
  # Serialize the value.
703
- serialized = serialize(value)
712
+ serialized = serializers.serialize(value)
704
713
  if serialized is not None:
705
714
  return serialized
706
715
 
@@ -803,7 +812,9 @@ def json_dumps(obj: Any) -> str:
803
812
  Returns:
804
813
  A string
805
814
  """
806
- return json.dumps(obj, ensure_ascii=False, default=serialize)
815
+ from reflex.utils import serializers
816
+
817
+ return json.dumps(obj, ensure_ascii=False, default=serializers.serialize)
807
818
 
808
819
 
809
820
  def unwrap_vars(value: str) -> str:
@@ -905,4 +916,7 @@ def format_data_editor_cell(cell: Any):
905
916
  Returns:
906
917
  The formatted cell.
907
918
  """
908
- return {"kind": Var.create(value="GridCellKind.Text"), "data": cell}
919
+ return {
920
+ "kind": Var.create(value="GridCellKind.Text", _var_is_string=False),
921
+ "data": cell,
922
+ }
reflex/utils/processes.py CHANGED
@@ -109,6 +109,33 @@ def change_port(port: str, _type: str) -> str:
109
109
  return new_port
110
110
 
111
111
 
112
+ def handle_port(service_name: str, port: str, default_port: str) -> str:
113
+ """Change port if the specified port is in use and is not explicitly specified as a CLI arg or config arg.
114
+ otherwise tell the user the port is in use and exit the app.
115
+
116
+ We make an assumption that when port is the default port,then it hasnt been explicitly set since its not straightforward
117
+ to know whether a port was explicitly provided by the user unless its any other than the default.
118
+
119
+ Args:
120
+ service_name: The frontend or backend.
121
+ port: The provided port.
122
+ default_port: The default port number associated with the specified service.
123
+
124
+ Returns:
125
+ The port to run the service on.
126
+
127
+ Raises:
128
+ Exit:when the port is in use.
129
+ """
130
+ if is_process_on_port(port):
131
+ if int(port) == int(default_port):
132
+ return change_port(port, service_name)
133
+ else:
134
+ console.error(f"{service_name.capitalize()} port: {port} is already in use")
135
+ raise typer.Exit()
136
+ return port
137
+
138
+
112
139
  def new_process(args, run: bool = False, show_logs: bool = False, **kwargs):
113
140
  """Wrapper over subprocess.Popen to unify the launch of child processes.
114
141
 
@@ -32,7 +32,7 @@ logger = logging.getLogger("pyi_generator")
32
32
  PWD = Path(".").resolve()
33
33
 
34
34
  EXCLUDED_FILES = [
35
- # "app.py",
35
+ "app.py",
36
36
  "component.py",
37
37
  "bare.py",
38
38
  "foreach.py",
@@ -424,7 +424,7 @@ def _generate_component_create_functiondef(
424
424
  ),
425
425
  ast.Constant(value=None),
426
426
  )
427
- for trigger in sorted(clz().get_event_triggers().keys())
427
+ for trigger in sorted(clz().get_event_triggers())
428
428
  )
429
429
  logger.debug(f"Generated {clz.__name__}.create method with {len(kwargs)} kwargs")
430
430
  create_args = ast.arguments(
@@ -488,7 +488,9 @@ def _generate_staticmethod_call_functiondef(
488
488
  kwonlyargs=[],
489
489
  kw_defaults=[],
490
490
  kwarg=ast.arg(arg="props"),
491
- defaults=[],
491
+ defaults=[ast.Constant(value=default) for default in fullspec.defaults]
492
+ if fullspec.defaults
493
+ else [],
492
494
  )
493
495
  definition = ast.FunctionDef(
494
496
  name="__call__",
@@ -854,7 +856,11 @@ class PyiGenerator:
854
856
  mode=black.mode.Mode(is_pyi=True),
855
857
  ).splitlines():
856
858
  # Bit of a hack here, since the AST cannot represent comments.
857
- if "def create(" in formatted_line or "Figure" in formatted_line:
859
+ if (
860
+ "def create(" in formatted_line
861
+ or "Figure" in formatted_line
862
+ or "Var[Template]" in formatted_line
863
+ ):
858
864
  pyi_content.append(formatted_line + " # type: ignore")
859
865
  else:
860
866
  pyi_content.append(formatted_line)
@@ -954,6 +960,7 @@ class PyiGenerator:
954
960
  target_path.is_file()
955
961
  and target_path.suffix == ".py"
956
962
  and target_path.name not in EXCLUDED_FILES
963
+ and "reflex/components" in str(target_path)
957
964
  ):
958
965
  file_targets.append(target_path)
959
966
  continue
@@ -2,30 +2,53 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import functools
5
6
  import json
6
7
  import types as builtin_types
7
8
  import warnings
8
9
  from datetime import date, datetime, time, timedelta
9
10
  from enum import Enum
10
11
  from pathlib import Path
11
- from typing import Any, Callable, Dict, List, Set, Tuple, Type, Union, get_type_hints
12
+ from typing import (
13
+ Any,
14
+ Callable,
15
+ Dict,
16
+ List,
17
+ Literal,
18
+ Optional,
19
+ Set,
20
+ Tuple,
21
+ Type,
22
+ Union,
23
+ get_type_hints,
24
+ overload,
25
+ )
12
26
 
13
27
  from reflex.base import Base
14
28
  from reflex.constants.colors import Color, format_color
15
- from reflex.utils import exceptions, format, types
29
+ from reflex.utils import exceptions, types
16
30
 
17
31
  # Mapping from type to a serializer.
18
32
  # The serializer should convert the type to a JSON object.
19
33
  SerializedType = Union[str, bool, int, float, list, dict]
34
+
35
+
20
36
  Serializer = Callable[[Type], SerializedType]
37
+
38
+
21
39
  SERIALIZERS: dict[Type, Serializer] = {}
40
+ SERIALIZER_TYPES: dict[Type, Type] = {}
22
41
 
23
42
 
24
- def serializer(fn: Serializer) -> Serializer:
43
+ def serializer(
44
+ fn: Serializer | None = None,
45
+ to: Type | None = None,
46
+ ) -> Serializer:
25
47
  """Decorator to add a serializer for a given type.
26
48
 
27
49
  Args:
28
50
  fn: The function to decorate.
51
+ to: The type returned by the serializer. If this is `str`, then any Var created from this type will be treated as a string.
29
52
 
30
53
  Returns:
31
54
  The decorated function.
@@ -33,8 +56,9 @@ def serializer(fn: Serializer) -> Serializer:
33
56
  Raises:
34
57
  ValueError: If the function does not take a single argument.
35
58
  """
36
- # Get the global serializers.
37
- global SERIALIZERS
59
+ if fn is None:
60
+ # If the function is not provided, return a partial that acts as a decorator.
61
+ return functools.partial(serializer, to=to) # type: ignore
38
62
 
39
63
  # Check the type hints to get the type of the argument.
40
64
  type_hints = get_type_hints(fn)
@@ -54,18 +78,47 @@ def serializer(fn: Serializer) -> Serializer:
54
78
  f"Serializer for type {type_} is already registered as {registered_fn.__qualname__}."
55
79
  )
56
80
 
81
+ # Apply type transformation if requested
82
+ if to is not None:
83
+ SERIALIZER_TYPES[type_] = to
84
+ get_serializer_type.cache_clear()
85
+
57
86
  # Register the serializer.
58
87
  SERIALIZERS[type_] = fn
88
+ get_serializer.cache_clear()
59
89
 
60
90
  # Return the function.
61
91
  return fn
62
92
 
63
93
 
64
- def serialize(value: Any) -> SerializedType | None:
94
+ @overload
95
+ def serialize(
96
+ value: Any, get_type: Literal[True]
97
+ ) -> Tuple[Optional[SerializedType], Optional[types.GenericType]]:
98
+ ...
99
+
100
+
101
+ @overload
102
+ def serialize(value: Any, get_type: Literal[False]) -> Optional[SerializedType]:
103
+ ...
104
+
105
+
106
+ @overload
107
+ def serialize(value: Any) -> Optional[SerializedType]:
108
+ ...
109
+
110
+
111
+ def serialize(
112
+ value: Any, get_type: bool = False
113
+ ) -> Union[
114
+ Optional[SerializedType],
115
+ Tuple[Optional[SerializedType], Optional[types.GenericType]],
116
+ ]:
65
117
  """Serialize the value to a JSON string.
66
118
 
67
119
  Args:
68
120
  value: The value to serialize.
121
+ get_type: Whether to return the type of the serialized value.
69
122
 
70
123
  Returns:
71
124
  The serialized value, or None if a serializer is not found.
@@ -75,13 +128,22 @@ def serialize(value: Any) -> SerializedType | None:
75
128
 
76
129
  # If there is no serializer, return None.
77
130
  if serializer is None:
131
+ if get_type:
132
+ return None, None
78
133
  return None
79
134
 
80
135
  # Serialize the value.
81
- return serializer(value)
136
+ serialized = serializer(value)
137
+
138
+ # Return the serialized value and the type.
139
+ if get_type:
140
+ return serialized, get_serializer_type(type(value))
141
+ else:
142
+ return serialized
82
143
 
83
144
 
84
- def get_serializer(type_: Type) -> Serializer | None:
145
+ @functools.lru_cache
146
+ def get_serializer(type_: Type) -> Optional[Serializer]:
85
147
  """Get the serializer for the type.
86
148
 
87
149
  Args:
@@ -90,8 +152,6 @@ def get_serializer(type_: Type) -> Serializer | None:
90
152
  Returns:
91
153
  The serializer for the type, or None if there is no serializer.
92
154
  """
93
- global SERIALIZERS
94
-
95
155
  # First, check if the type is registered.
96
156
  serializer = SERIALIZERS.get(type_)
97
157
  if serializer is not None:
@@ -106,6 +166,30 @@ def get_serializer(type_: Type) -> Serializer | None:
106
166
  return None
107
167
 
108
168
 
169
+ @functools.lru_cache
170
+ def get_serializer_type(type_: Type) -> Optional[Type]:
171
+ """Get the converted type for the type after serializing.
172
+
173
+ Args:
174
+ type_: The type to get the serializer type for.
175
+
176
+ Returns:
177
+ The serialized type for the type, or None if there is no type conversion registered.
178
+ """
179
+ # First, check if the type is registered.
180
+ serializer = SERIALIZER_TYPES.get(type_)
181
+ if serializer is not None:
182
+ return serializer
183
+
184
+ # If the type is not registered, check if it is a subclass of a registered type.
185
+ for registered_type, serializer in reversed(SERIALIZER_TYPES.items()):
186
+ if types._issubclass(type_, registered_type):
187
+ return serializer
188
+
189
+ # If there is no serializer, return None.
190
+ return None
191
+
192
+
109
193
  def has_serializer(type_: Type) -> bool:
110
194
  """Check if there is a serializer for the type.
111
195
 
@@ -118,7 +202,7 @@ def has_serializer(type_: Type) -> bool:
118
202
  return get_serializer(type_) is not None
119
203
 
120
204
 
121
- @serializer
205
+ @serializer(to=str)
122
206
  def serialize_type(value: type) -> str:
123
207
  """Serialize a python type.
124
208
 
@@ -154,6 +238,8 @@ def serialize_primitive(value: Union[bool, int, float, None]) -> str:
154
238
  Returns:
155
239
  The serialized number/bool/None.
156
240
  """
241
+ from reflex.utils import format
242
+
157
243
  return format.json_dumps(value)
158
244
 
159
245
 
@@ -180,6 +266,8 @@ def serialize_list(value: Union[List, Tuple, Set]) -> str:
180
266
  Returns:
181
267
  The serialized list.
182
268
  """
269
+ from reflex.utils import format
270
+
183
271
  # Dump the list to a string.
184
272
  fprop = format.json_dumps(list(value))
185
273
 
@@ -202,6 +290,7 @@ def serialize_dict(prop: Dict[str, Any]) -> str:
202
290
  """
203
291
  # Import here to avoid circular imports.
204
292
  from reflex.event import EventHandler
293
+ from reflex.utils import format
205
294
 
206
295
  prop_dict = {}
207
296
 
@@ -221,7 +310,7 @@ def serialize_dict(prop: Dict[str, Any]) -> str:
221
310
  return format.unwrap_vars(fprop)
222
311
 
223
312
 
224
- @serializer
313
+ @serializer(to=str)
225
314
  def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
226
315
  """Serialize a datetime to a JSON string.
227
316
 
@@ -234,8 +323,8 @@ def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
234
323
  return str(dt)
235
324
 
236
325
 
237
- @serializer
238
- def serialize_path(path: Path):
326
+ @serializer(to=str)
327
+ def serialize_path(path: Path) -> str:
239
328
  """Serialize a pathlib.Path to a JSON string.
240
329
 
241
330
  Args:
@@ -260,7 +349,7 @@ def serialize_enum(en: Enum) -> str:
260
349
  return en.value
261
350
 
262
351
 
263
- @serializer
352
+ @serializer(to=str)
264
353
  def serialize_color(color: Color) -> str:
265
354
  """Serialize a color.
266
355
 
@@ -309,7 +398,7 @@ except ImportError:
309
398
  pass
310
399
 
311
400
  try:
312
- from plotly.graph_objects import Figure
401
+ from plotly.graph_objects import Figure, layout
313
402
  from plotly.io import to_json
314
403
 
315
404
  @serializer
@@ -324,6 +413,21 @@ try:
324
413
  """
325
414
  return json.loads(str(to_json(figure)))
326
415
 
416
+ @serializer
417
+ def serialize_template(template: layout.Template) -> dict:
418
+ """Serialize a plotly template.
419
+
420
+ Args:
421
+ template: The template to serialize.
422
+
423
+ Returns:
424
+ The serialized template.
425
+ """
426
+ return {
427
+ "data": json.loads(str(to_json(template.data))),
428
+ "layout": json.loads(str(to_json(template.layout))),
429
+ }
430
+
327
431
  except ImportError:
328
432
  pass
329
433
 
reflex/utils/types.py CHANGED
@@ -42,7 +42,7 @@ from sqlalchemy.orm import (
42
42
 
43
43
  from reflex import constants
44
44
  from reflex.base import Base
45
- from reflex.utils import console, serializers
45
+ from reflex.utils import console
46
46
 
47
47
  if sys.version_info >= (3, 12):
48
48
  from typing import override
@@ -215,7 +215,11 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
215
215
  attr = getattr(cls, name, None)
216
216
  if hint := get_property_hint(attr):
217
217
  return hint
218
- if hasattr(cls, "__fields__") and name in cls.__fields__:
218
+ if (
219
+ hasattr(cls, "__fields__")
220
+ and name in cls.__fields__
221
+ and hasattr(cls.__fields__[name], "outer_type_")
222
+ ):
219
223
  # pydantic models
220
224
  field = cls.__fields__[name]
221
225
  type_ = field.outer_type_
@@ -392,6 +396,8 @@ def is_valid_var_type(type_: Type) -> bool:
392
396
  Returns:
393
397
  Whether the type is a valid prop type.
394
398
  """
399
+ from reflex.utils import serializers
400
+
395
401
  if is_union(type_):
396
402
  return all((is_valid_var_type(arg) for arg in get_args(type_)))
397
403
  return _issubclass(type_, StateVar) or serializers.has_serializer(type_)
@@ -503,7 +509,7 @@ def validate_parameter_literals(func):
503
509
  annotations = {param[0]: param[1].annotation for param in func_params}
504
510
 
505
511
  # validate args
506
- for param, arg in zip(annotations.keys(), args):
512
+ for param, arg in zip(annotations, args):
507
513
  if annotations[param] is inspect.Parameter.empty:
508
514
  continue
509
515
  validate_literal(param, arg, annotations[param], func.__name__)