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
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,13 +2,27 @@
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
@@ -17,15 +31,24 @@ from reflex.utils import exceptions, types
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)
82
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
83
143
 
84
- def get_serializer(type_: Type) -> Serializer | None:
144
+
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
 
@@ -226,7 +310,7 @@ def serialize_dict(prop: Dict[str, Any]) -> str:
226
310
  return format.unwrap_vars(fprop)
227
311
 
228
312
 
229
- @serializer
313
+ @serializer(to=str)
230
314
  def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
231
315
  """Serialize a datetime to a JSON string.
232
316
 
@@ -239,8 +323,8 @@ def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
239
323
  return str(dt)
240
324
 
241
325
 
242
- @serializer
243
- def serialize_path(path: Path):
326
+ @serializer(to=str)
327
+ def serialize_path(path: Path) -> str:
244
328
  """Serialize a pathlib.Path to a JSON string.
245
329
 
246
330
  Args:
@@ -265,7 +349,7 @@ def serialize_enum(en: Enum) -> str:
265
349
  return en.value
266
350
 
267
351
 
268
- @serializer
352
+ @serializer(to=str)
269
353
  def serialize_color(color: Color) -> str:
270
354
  """Serialize a color.
271
355
 
@@ -314,7 +398,7 @@ except ImportError:
314
398
  pass
315
399
 
316
400
  try:
317
- from plotly.graph_objects import Figure
401
+ from plotly.graph_objects import Figure, layout
318
402
  from plotly.io import to_json
319
403
 
320
404
  @serializer
@@ -329,6 +413,21 @@ try:
329
413
  """
330
414
  return json.loads(str(to_json(figure)))
331
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
+
332
431
  except ImportError:
333
432
  pass
334
433
 
reflex/utils/types.py CHANGED
@@ -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_
@@ -505,7 +509,7 @@ def validate_parameter_literals(func):
505
509
  annotations = {param[0]: param[1].annotation for param in func_params}
506
510
 
507
511
  # validate args
508
- for param, arg in zip(annotations.keys(), args):
512
+ for param, arg in zip(annotations, args):
509
513
  if annotations[param] is inspect.Parameter.empty:
510
514
  continue
511
515
  validate_literal(param, arg, annotations[param], func.__name__)
reflex/vars.py CHANGED
@@ -347,7 +347,7 @@ class Var:
347
347
  cls,
348
348
  value: Any,
349
349
  _var_is_local: bool = True,
350
- _var_is_string: bool = False,
350
+ _var_is_string: bool | None = None,
351
351
  _var_data: Optional[VarData] = None,
352
352
  ) -> Var | None:
353
353
  """Create a var from a value.
@@ -380,18 +380,39 @@ class Var:
380
380
 
381
381
  # Try to serialize the value.
382
382
  type_ = type(value)
383
- name = value if type_ in types.JSONType else serializers.serialize(value)
383
+ if type_ in types.JSONType:
384
+ name = value
385
+ else:
386
+ name, serialized_type = serializers.serialize(value, get_type=True)
387
+ if (
388
+ serialized_type is not None
389
+ and _var_is_string is None
390
+ and issubclass(serialized_type, str)
391
+ ):
392
+ _var_is_string = True
384
393
  if name is None:
385
394
  raise VarTypeError(
386
395
  f"No JSON serializer found for var {value} of type {type_}."
387
396
  )
388
397
  name = name if isinstance(name, str) else format.json_dumps(name)
389
398
 
399
+ if _var_is_string is None and type_ is str:
400
+ console.deprecate(
401
+ feature_name="Creating a Var from a string without specifying _var_is_string",
402
+ reason=(
403
+ "Specify _var_is_string=False to create a Var that is not a string literal. "
404
+ "In the future, creating a Var from a string will be treated as a string literal "
405
+ "by default."
406
+ ),
407
+ deprecation_version="0.5.4",
408
+ removal_version="0.6.0",
409
+ )
410
+
390
411
  return BaseVar(
391
412
  _var_name=name,
392
413
  _var_type=type_,
393
414
  _var_is_local=_var_is_local,
394
- _var_is_string=_var_is_string,
415
+ _var_is_string=_var_is_string if _var_is_string is not None else False,
395
416
  _var_data=_var_data,
396
417
  )
397
418
 
@@ -400,7 +421,7 @@ class Var:
400
421
  cls,
401
422
  value: Any,
402
423
  _var_is_local: bool = True,
403
- _var_is_string: bool = False,
424
+ _var_is_string: bool | None = None,
404
425
  _var_data: Optional[VarData] = None,
405
426
  ) -> Var:
406
427
  """Create a var from a value, asserting that it is not None.
@@ -531,6 +552,14 @@ class Var:
531
552
  fn = "JSON.stringify" if json else "String"
532
553
  return self.operation(fn=fn, type_=str)
533
554
 
555
+ def to_int(self) -> Var:
556
+ """Convert a var to an int.
557
+
558
+ Returns:
559
+ The parseInt var.
560
+ """
561
+ return self.operation(fn="parseInt", type_=int)
562
+
534
563
  def __hash__(self) -> int:
535
564
  """Define a hash function for a var.
536
565
 
@@ -847,19 +876,19 @@ class Var:
847
876
  if invoke_fn:
848
877
  # invoke the function on left operand.
849
878
  operation_name = (
850
- f"{left_operand_full_name}.{fn}({right_operand_full_name})"
851
- ) # type: ignore
879
+ f"{left_operand_full_name}.{fn}({right_operand_full_name})" # type: ignore
880
+ )
852
881
  else:
853
882
  # pass the operands as arguments to the function.
854
883
  operation_name = (
855
- f"{left_operand_full_name} {op} {right_operand_full_name}"
856
- ) # type: ignore
884
+ f"{left_operand_full_name} {op} {right_operand_full_name}" # type: ignore
885
+ )
857
886
  operation_name = f"{fn}({operation_name})"
858
887
  else:
859
888
  # apply operator to operands (left operand <operator> right_operand)
860
889
  operation_name = (
861
- f"{left_operand_full_name} {op} {right_operand_full_name}"
862
- ) # type: ignore
890
+ f"{left_operand_full_name} {op} {right_operand_full_name}" # type: ignore
891
+ )
863
892
  operation_name = format.wrap(operation_name, "(")
864
893
  else:
865
894
  # apply operator to left operand (<operator> left_operand)
reflex/vars.pyi CHANGED
@@ -51,11 +51,11 @@ class Var:
51
51
  _var_data: VarData | None = None
52
52
  @classmethod
53
53
  def create(
54
- cls, value: Any, _var_is_local: bool = False, _var_is_string: bool = False, _var_data: VarData | None = None,
54
+ cls, value: Any, _var_is_local: bool = True, _var_is_string: bool | None = None, _var_data: VarData | None = None,
55
55
  ) -> Optional[Var]: ...
56
56
  @classmethod
57
57
  def create_safe(
58
- cls, value: Any, _var_is_local: bool = False, _var_is_string: bool = False, _var_data: VarData | None = None,
58
+ cls, value: Any, _var_is_local: bool = True, _var_is_string: bool | None = None, _var_data: VarData | None = None,
59
59
  ) -> Var: ...
60
60
  @classmethod
61
61
  def __class_getitem__(cls, type_: Type) -> _GenericAlias: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: reflex
3
- Version: 0.5.3a2
3
+ Version: 0.5.4a1
4
4
  Summary: Web apps in pure Python.
5
5
  Home-page: https://reflex.dev
6
6
  License: Apache-2.0