reflex 0.6.0a3__py3-none-any.whl → 0.6.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 (61) hide show
  1. reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +1 -1
  2. reflex/.templates/jinja/web/pages/_app.js.jinja2 +14 -0
  3. reflex/.templates/web/utils/state.js +70 -41
  4. reflex/app.py +11 -11
  5. reflex/app_mixins/lifespan.py +24 -6
  6. reflex/app_module_for_backend.py +1 -1
  7. reflex/base.py +7 -13
  8. reflex/compiler/utils.py +17 -8
  9. reflex/components/base/bare.py +3 -1
  10. reflex/components/base/meta.py +5 -3
  11. reflex/components/component.py +28 -20
  12. reflex/components/core/breakpoints.py +1 -3
  13. reflex/components/core/cond.py +4 -4
  14. reflex/components/datadisplay/__init__.py +0 -1
  15. reflex/components/datadisplay/__init__.pyi +0 -1
  16. reflex/components/datadisplay/code.py +93 -106
  17. reflex/components/datadisplay/code.pyi +710 -53
  18. reflex/components/datadisplay/logo.py +22 -20
  19. reflex/components/dynamic.py +157 -0
  20. reflex/components/el/elements/forms.py +4 -1
  21. reflex/components/gridjs/datatable.py +2 -1
  22. reflex/components/markdown/markdown.py +10 -6
  23. reflex/components/markdown/markdown.pyi +3 -0
  24. reflex/components/radix/themes/components/progress.py +22 -0
  25. reflex/components/radix/themes/components/progress.pyi +2 -0
  26. reflex/components/radix/themes/components/segmented_control.py +3 -0
  27. reflex/components/radix/themes/components/segmented_control.pyi +2 -0
  28. reflex/components/radix/themes/layout/stack.py +1 -1
  29. reflex/components/recharts/cartesian.py +1 -1
  30. reflex/components/sonner/toast.py +3 -3
  31. reflex/components/tags/iter_tag.py +5 -1
  32. reflex/config.py +2 -2
  33. reflex/constants/base.py +4 -1
  34. reflex/constants/installer.py +8 -1
  35. reflex/event.py +63 -22
  36. reflex/experimental/assets.py +3 -1
  37. reflex/experimental/client_state.py +12 -7
  38. reflex/experimental/misc.py +5 -3
  39. reflex/middleware/hydrate_middleware.py +1 -2
  40. reflex/page.py +10 -3
  41. reflex/reflex.py +20 -3
  42. reflex/state.py +105 -44
  43. reflex/style.py +12 -2
  44. reflex/testing.py +8 -4
  45. reflex/utils/console.py +1 -1
  46. reflex/utils/exceptions.py +4 -0
  47. reflex/utils/exec.py +170 -18
  48. reflex/utils/format.py +6 -44
  49. reflex/utils/path_ops.py +36 -1
  50. reflex/utils/prerequisites.py +62 -21
  51. reflex/utils/serializers.py +7 -46
  52. reflex/utils/telemetry.py +1 -1
  53. reflex/utils/types.py +18 -3
  54. reflex/vars/base.py +303 -43
  55. reflex/vars/number.py +3 -0
  56. reflex/vars/sequence.py +43 -8
  57. {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/METADATA +5 -5
  58. {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/RECORD +61 -60
  59. {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/LICENSE +0 -0
  60. {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/WHEEL +0 -0
  61. {reflex-0.6.0a3.dist-info → reflex-0.6.1.dist-info}/entry_points.txt +0 -0
reflex/event.py CHANGED
@@ -18,10 +18,12 @@ from typing import (
18
18
  get_type_hints,
19
19
  )
20
20
 
21
+ from typing_extensions import get_args, get_origin
22
+
21
23
  from reflex import constants
22
24
  from reflex.utils import format
23
25
  from reflex.utils.exceptions import EventFnArgMismatch, EventHandlerArgMismatch
24
- from reflex.utils.types import ArgsSpec
26
+ from reflex.utils.types import ArgsSpec, GenericType
25
27
  from reflex.vars import VarData
26
28
  from reflex.vars.base import LiteralVar, Var
27
29
  from reflex.vars.function import FunctionStringVar, FunctionVar
@@ -217,7 +219,7 @@ class EventHandler(EventActionsMixin):
217
219
  raise EventHandlerTypeError(
218
220
  f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
219
221
  ) from e
220
- payload = tuple(zip(fn_args, values, strict=False))
222
+ payload = tuple(zip(fn_args, values))
221
223
 
222
224
  # Return the event spec.
223
225
  return EventSpec(
@@ -310,7 +312,7 @@ class EventSpec(EventActionsMixin):
310
312
  raise EventHandlerTypeError(
311
313
  f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
312
314
  ) from e
313
- new_payload = tuple(zip(fn_args, values, strict=False))
315
+ new_payload = tuple(zip(fn_args, values))
314
316
  return self.with_args(self.args + new_payload)
315
317
 
316
318
 
@@ -417,7 +419,7 @@ class FileUpload:
417
419
  on_upload_progress: Optional[Union[EventHandler, Callable]] = None
418
420
 
419
421
  @staticmethod
420
- def on_upload_progress_args_spec(_prog: Dict[str, Union[int, float, bool]]):
422
+ def on_upload_progress_args_spec(_prog: Var[Dict[str, Union[int, float, bool]]]):
421
423
  """Args spec for on_upload_progress event handler.
422
424
 
423
425
  Returns:
@@ -910,6 +912,20 @@ def call_event_handler(
910
912
  )
911
913
 
912
914
 
915
+ def unwrap_var_annotation(annotation: GenericType):
916
+ """Unwrap a Var annotation or return it as is if it's not Var[X].
917
+
918
+ Args:
919
+ annotation: The annotation to unwrap.
920
+
921
+ Returns:
922
+ The unwrapped annotation.
923
+ """
924
+ if get_origin(annotation) is Var and (args := get_args(annotation)):
925
+ return args[0]
926
+ return annotation
927
+
928
+
913
929
  def parse_args_spec(arg_spec: ArgsSpec):
914
930
  """Parse the args provided in the ArgsSpec of an event trigger.
915
931
 
@@ -921,20 +937,54 @@ def parse_args_spec(arg_spec: ArgsSpec):
921
937
  """
922
938
  spec = inspect.getfullargspec(arg_spec)
923
939
  annotations = get_type_hints(arg_spec)
940
+
924
941
  return arg_spec(
925
942
  *[
926
- Var(f"_{l_arg}").to(annotations.get(l_arg, FrontendEvent))
943
+ Var(f"_{l_arg}").to(
944
+ unwrap_var_annotation(annotations.get(l_arg, FrontendEvent))
945
+ )
927
946
  for l_arg in spec.args
928
947
  ]
929
948
  )
930
949
 
931
950
 
951
+ def check_fn_match_arg_spec(fn: Callable, arg_spec: ArgsSpec) -> List[Var]:
952
+ """Ensures that the function signature matches the passed argument specification
953
+ or raises an EventFnArgMismatch if they do not.
954
+
955
+ Args:
956
+ fn: The function to be validated.
957
+ arg_spec: The argument specification for the event trigger.
958
+
959
+ Returns:
960
+ The parsed arguments from the argument specification.
961
+
962
+ Raises:
963
+ EventFnArgMismatch: Raised if the number of mandatory arguments do not match
964
+ """
965
+ fn_args = inspect.getfullargspec(fn).args
966
+ fn_defaults_args = inspect.getfullargspec(fn).defaults
967
+ n_fn_args = len(fn_args)
968
+ n_fn_defaults_args = len(fn_defaults_args) if fn_defaults_args else 0
969
+ if isinstance(fn, types.MethodType):
970
+ n_fn_args -= 1 # subtract 1 for bound self arg
971
+ parsed_args = parse_args_spec(arg_spec)
972
+ if not (n_fn_args - n_fn_defaults_args <= len(parsed_args) <= n_fn_args):
973
+ raise EventFnArgMismatch(
974
+ "The number of mandatory arguments accepted by "
975
+ f"{fn} ({n_fn_args - n_fn_defaults_args}) "
976
+ "does not match the arguments passed by the event trigger: "
977
+ f"{[str(v) for v in parsed_args]}\n"
978
+ "See https://reflex.dev/docs/events/event-arguments/"
979
+ )
980
+ return parsed_args
981
+
982
+
932
983
  def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var:
933
984
  """Call a function to a list of event specs.
934
985
 
935
986
  The function should return a single EventSpec, a list of EventSpecs, or a
936
- single Var. The function signature must match the passed arg_spec or
937
- EventFnArgsMismatch will be raised.
987
+ single Var.
938
988
 
939
989
  Args:
940
990
  fn: The function to call.
@@ -944,7 +994,6 @@ def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var:
944
994
  The event specs from calling the function or a Var.
945
995
 
946
996
  Raises:
947
- EventFnArgMismatch: If the function signature doesn't match the arg spec.
948
997
  EventHandlerValueError: If the lambda returns an unusable value.
949
998
  """
950
999
  # Import here to avoid circular imports.
@@ -952,19 +1001,7 @@ def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var:
952
1001
  from reflex.utils.exceptions import EventHandlerValueError
953
1002
 
954
1003
  # Check that fn signature matches arg_spec
955
- fn_args = inspect.getfullargspec(fn).args
956
- n_fn_args = len(fn_args)
957
- if isinstance(fn, types.MethodType):
958
- n_fn_args -= 1 # subtract 1 for bound self arg
959
- parsed_args = parse_args_spec(arg_spec)
960
- if len(parsed_args) != n_fn_args:
961
- raise EventFnArgMismatch(
962
- "The number of arguments accepted by "
963
- f"{fn} ({n_fn_args}) "
964
- "does not match the arguments passed by the event trigger: "
965
- f"{[str(v) for v in parsed_args]}\n"
966
- "See https://reflex.dev/docs/events/event-arguments/"
967
- )
1004
+ parsed_args = check_fn_match_arg_spec(fn, arg_spec)
968
1005
 
969
1006
  # Call the function with the parsed args.
970
1007
  out = fn(*parsed_args)
@@ -1025,6 +1062,9 @@ def fix_events(
1025
1062
  token: The user token.
1026
1063
  router_data: The optional router data to set in the event.
1027
1064
 
1065
+ Raises:
1066
+ ValueError: If the event type is not what was expected.
1067
+
1028
1068
  Returns:
1029
1069
  The fixed events.
1030
1070
  """
@@ -1048,7 +1088,8 @@ def fix_events(
1048
1088
  # Otherwise, create an event from the event spec.
1049
1089
  if isinstance(e, EventHandler):
1050
1090
  e = e()
1051
- assert isinstance(e, EventSpec), f"Unexpected event type, {type(e)}."
1091
+ if not isinstance(e, EventSpec):
1092
+ raise ValueError(f"Unexpected event type, {type(e)}.")
1052
1093
  name = format.format_event_handler(e.handler)
1053
1094
  payload = {k._js_expr: v._decode() for k, v in e.args} # type: ignore
1054
1095
 
@@ -24,6 +24,7 @@ def asset(relative_filename: str, subfolder: Optional[str] = None) -> str:
24
24
 
25
25
  Raises:
26
26
  FileNotFoundError: If the file does not exist.
27
+ ValueError: If the module is None.
27
28
 
28
29
  Returns:
29
30
  The relative URL to the copied asset.
@@ -31,7 +32,8 @@ def asset(relative_filename: str, subfolder: Optional[str] = None) -> str:
31
32
  # Determine the file by which the asset is exposed.
32
33
  calling_file = inspect.stack()[1].filename
33
34
  module = inspect.getmodule(inspect.stack()[1][0])
34
- assert module is not None
35
+ if module is None:
36
+ raise ValueError("Module is None")
35
37
  caller_module_path = module.__name__.replace(".", "/")
36
38
 
37
39
  subfolder = f"{caller_module_path}/{subfolder}" if subfolder else caller_module_path
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import dataclasses
6
+ import re
6
7
  import sys
7
8
  from typing import Any, Callable, Union
8
9
 
@@ -90,12 +91,16 @@ class ClientStateVar(Var):
90
91
  default: The default value of the variable.
91
92
  global_ref: Whether the state should be accessible in any Component and on the backend.
92
93
 
94
+ Raises:
95
+ ValueError: If the var_name is not a string.
96
+
93
97
  Returns:
94
98
  ClientStateVar
95
99
  """
96
100
  if var_name is None:
97
101
  var_name = get_unique_variable_name()
98
- assert isinstance(var_name, str), "var_name must be a string."
102
+ if not isinstance(var_name, str):
103
+ raise ValueError("var_name must be a string.")
99
104
  if default is NoValue:
100
105
  default_var = Var(_js_expr="")
101
106
  elif not isinstance(default, Var):
@@ -174,15 +179,15 @@ class ClientStateVar(Var):
174
179
  else self._setter_name
175
180
  )
176
181
  if value is not NoValue:
177
- import re
178
-
179
182
  # This is a hack to make it work like an EventSpec taking an arg
180
183
  value_str = str(LiteralVar.create(value))
181
184
 
182
- # remove patterns of ["*"] from the value_str using regex
183
- arg = re.sub(r"\[\".*\"\]", "", value_str)
184
-
185
- setter = f"({arg}) => {setter}({str(value)})"
185
+ if value_str.startswith("_"):
186
+ # remove patterns of ["*"] from the value_str using regex
187
+ arg = re.sub(r"\[\".*\"\]", "", value_str)
188
+ setter = f"(({arg}) => {setter}({value_str}))"
189
+ else:
190
+ setter = f"(() => {setter}({value_str}))"
186
191
  return Var(
187
192
  _js_expr=setter,
188
193
  _var_data=VarData(imports=_refs_import if self._global_ref else {}),
@@ -12,10 +12,12 @@ async def run_in_thread(func) -> Any:
12
12
  Args:
13
13
  func (callable): The non-async function to run.
14
14
 
15
+ Raises:
16
+ ValueError: If the function is an async function.
17
+
15
18
  Returns:
16
19
  Any: The return value of the function.
17
20
  """
18
- assert not asyncio.coroutines.iscoroutinefunction(
19
- func
20
- ), "func must be a non-async function"
21
+ if asyncio.coroutines.iscoroutinefunction(func):
22
+ raise ValueError("func must be a non-async function")
21
23
  return await asyncio.get_event_loop().run_in_executor(None, func)
@@ -9,7 +9,6 @@ from reflex import constants
9
9
  from reflex.event import Event, get_hydrate_event
10
10
  from reflex.middleware.middleware import Middleware
11
11
  from reflex.state import BaseState, StateUpdate
12
- from reflex.utils import format
13
12
 
14
13
  if TYPE_CHECKING:
15
14
  from reflex.app import App
@@ -43,7 +42,7 @@ class HydrateMiddleware(Middleware):
43
42
  setattr(state, constants.CompileVars.IS_HYDRATED, False)
44
43
 
45
44
  # Get the initial state.
46
- delta = format.format_state(state.dict())
45
+ delta = state.dict()
47
46
  # since a full dict was captured, clean any dirtiness
48
47
  state._clean()
49
48
 
reflex/page.py CHANGED
@@ -65,13 +65,20 @@ def page(
65
65
  return decorator
66
66
 
67
67
 
68
- def get_decorated_pages() -> list[dict]:
68
+ def get_decorated_pages(omit_implicit_routes=True) -> list[dict[str, Any]]:
69
69
  """Get the decorated pages.
70
70
 
71
+ Args:
72
+ omit_implicit_routes: Whether to omit pages where the route will be implicitely guessed later.
73
+
71
74
  Returns:
72
75
  The decorated pages.
73
76
  """
74
77
  return sorted(
75
- [page_data for _, page_data in DECORATED_PAGES[get_config().app_name]],
76
- key=lambda x: x["route"],
78
+ [
79
+ page_data
80
+ for _, page_data in DECORATED_PAGES[get_config().app_name]
81
+ if not omit_implicit_routes or "route" in page_data
82
+ ],
83
+ key=lambda x: x.get("route", ""),
77
84
  )
reflex/reflex.py CHANGED
@@ -15,6 +15,7 @@ from reflex_cli.utils import dependency
15
15
 
16
16
  from reflex import constants
17
17
  from reflex.config import get_config
18
+ from reflex.constants.base import LogLevel
18
19
  from reflex.custom_components.custom_components import custom_components_cli
19
20
  from reflex.state import reset_disk_state_manager
20
21
  from reflex.utils import console, redir, telemetry
@@ -229,7 +230,8 @@ def _run(
229
230
  exec.run_frontend_prod,
230
231
  exec.run_backend_prod,
231
232
  )
232
- assert setup_frontend and frontend_cmd and backend_cmd, "Invalid env"
233
+ if not setup_frontend or not frontend_cmd or not backend_cmd:
234
+ raise ValueError("Invalid env")
233
235
 
234
236
  # Post a telemetry event.
235
237
  telemetry.send(f"run-{env.value}")
@@ -245,15 +247,30 @@ def _run(
245
247
  setup_frontend(Path.cwd())
246
248
  commands.append((frontend_cmd, Path.cwd(), frontend_port, backend))
247
249
 
250
+ # If no loglevel is specified, set the subprocesses loglevel to WARNING.
251
+ subprocesses_loglevel = (
252
+ loglevel if loglevel != LogLevel.DEFAULT else LogLevel.WARNING
253
+ )
254
+
248
255
  # In prod mode, run the backend on a separate thread.
249
256
  if backend and env == constants.Env.PROD:
250
- commands.append((backend_cmd, backend_host, backend_port))
257
+ commands.append(
258
+ (
259
+ backend_cmd,
260
+ backend_host,
261
+ backend_port,
262
+ subprocesses_loglevel,
263
+ frontend,
264
+ )
265
+ )
251
266
 
252
267
  # Start the frontend and backend.
253
268
  with processes.run_concurrently_context(*commands):
254
269
  # In dev mode, run the backend on the main thread.
255
270
  if backend and env == constants.Env.DEV:
256
- backend_cmd(backend_host, int(backend_port))
271
+ backend_cmd(
272
+ backend_host, int(backend_port), subprocesses_loglevel, frontend
273
+ )
257
274
  # The windows uvicorn bug workaround
258
275
  # https://github.com/reflex-dev/reflex/issues/2335
259
276
  if constants.IS_WINDOWS and exec.frontend_process:
reflex/state.py CHANGED
@@ -12,6 +12,7 @@ import os
12
12
  import uuid
13
13
  from abc import ABC, abstractmethod
14
14
  from collections import defaultdict
15
+ from hashlib import md5
15
16
  from pathlib import Path
16
17
  from types import FunctionType, MethodType
17
18
  from typing import (
@@ -29,10 +30,12 @@ from typing import (
29
30
  Type,
30
31
  Union,
31
32
  cast,
33
+ get_type_hints,
32
34
  )
33
35
 
34
36
  import dill
35
37
  from sqlalchemy.orm import DeclarativeBase
38
+ from typing_extensions import Self
36
39
 
37
40
  from reflex.config import get_config
38
41
  from reflex.vars.base import (
@@ -40,6 +43,8 @@ from reflex.vars.base import (
40
43
  DynamicRouteVar,
41
44
  Var,
42
45
  computed_var,
46
+ dispatch,
47
+ get_unique_variable_name,
43
48
  is_computed_var,
44
49
  )
45
50
 
@@ -71,7 +76,7 @@ from reflex.utils.exceptions import (
71
76
  LockExpiredError,
72
77
  )
73
78
  from reflex.utils.exec import is_testing_env
74
- from reflex.utils.serializers import SerializedType, serialize, serializer
79
+ from reflex.utils.serializers import serializer
75
80
  from reflex.utils.types import override
76
81
  from reflex.vars import VarData
77
82
 
@@ -336,6 +341,29 @@ class EventHandlerSetVar(EventHandler):
336
341
  return super().__call__(*args)
337
342
 
338
343
 
344
+ if TYPE_CHECKING:
345
+ from pydantic.v1.fields import ModelField
346
+
347
+
348
+ def get_var_for_field(cls: Type[BaseState], f: ModelField):
349
+ """Get a Var instance for a Pydantic field.
350
+
351
+ Args:
352
+ cls: The state class.
353
+ f: The Pydantic field.
354
+
355
+ Returns:
356
+ The Var instance.
357
+ """
358
+ field_name = format.format_state_name(cls.get_full_name()) + "." + f.name
359
+
360
+ return dispatch(
361
+ field_name=field_name,
362
+ var_data=VarData.from_state(cls, f.name),
363
+ result_var_type=f.outer_type_,
364
+ )
365
+
366
+
339
367
  class BaseState(Base, ABC, extra=pydantic.Extra.allow):
340
368
  """The state of the app."""
341
369
 
@@ -548,6 +576,15 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
548
576
  for name, value in cls.__dict__.items()
549
577
  if types.is_backend_base_variable(name, cls)
550
578
  }
579
+ # Add annotated backend vars that do not have a default value.
580
+ new_backend_vars.update(
581
+ {
582
+ name: Var("", _var_type=annotation_value).get_default_value()
583
+ for name, annotation_value in get_type_hints(cls).items()
584
+ if name not in new_backend_vars
585
+ and types.is_backend_base_variable(name, cls)
586
+ }
587
+ )
551
588
 
552
589
  cls.backend_vars = {
553
590
  **cls.inherited_backend_vars,
@@ -556,11 +593,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
556
593
 
557
594
  # Set the base and computed vars.
558
595
  cls.base_vars = {
559
- f.name: Var(
560
- _js_expr=format.format_state_name(cls.get_full_name()) + "." + f.name,
561
- _var_type=f.outer_type_,
562
- _var_data=VarData.from_state(cls),
563
- ).guess_type()
596
+ f.name: get_var_for_field(cls, f)
564
597
  for f in cls.get_fields().values()
565
598
  if f.name not in cls.get_skip_vars()
566
599
  }
@@ -664,6 +697,36 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
664
697
  and hasattr(value, "__code__")
665
698
  )
666
699
 
700
+ @classmethod
701
+ def _evaluate(cls, f: Callable[[Self], Any]) -> Var:
702
+ """Evaluate a function to a ComputedVar. Experimental.
703
+
704
+ Args:
705
+ f: The function to evaluate.
706
+
707
+ Returns:
708
+ The ComputedVar.
709
+ """
710
+ console.warn(
711
+ "The _evaluate method is experimental and may be removed in future versions."
712
+ )
713
+ from reflex.components.base.fragment import fragment
714
+ from reflex.components.component import Component
715
+
716
+ unique_var_name = get_unique_variable_name()
717
+
718
+ @computed_var(_js_expr=unique_var_name, return_type=Component)
719
+ def computed_var_func(state: Self):
720
+ return fragment(f(state))
721
+
722
+ setattr(cls, unique_var_name, computed_var_func)
723
+ cls.computed_vars[unique_var_name] = computed_var_func
724
+ cls.vars[unique_var_name] = computed_var_func
725
+ cls._update_substate_inherited_vars({unique_var_name: computed_var_func})
726
+ cls._always_dirty_computed_vars.add(unique_var_name)
727
+
728
+ return getattr(cls, unique_var_name)
729
+
667
730
  @classmethod
668
731
  def _mixins(cls) -> List[Type]:
669
732
  """Get the mixin classes of the state.
@@ -807,6 +870,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
807
870
  def get_parent_state(cls) -> Type[BaseState] | None:
808
871
  """Get the parent state.
809
872
 
873
+ Raises:
874
+ ValueError: If more than one parent state is found.
875
+
810
876
  Returns:
811
877
  The parent state.
812
878
  """
@@ -815,9 +881,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
815
881
  for base in cls.__bases__
816
882
  if issubclass(base, BaseState) and base is not BaseState and not base._mixin
817
883
  ]
818
- assert (
819
- len(parent_states) < 2
820
- ), f"Only one parent state is allowed {parent_states}."
884
+ if len(parent_states) >= 2:
885
+ raise ValueError(f"Only one parent state is allowed {parent_states}.")
821
886
  return parent_states[0] if len(parent_states) == 1 else None # type: ignore
822
887
 
823
888
  @classmethod
@@ -948,7 +1013,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
948
1013
  var = Var(
949
1014
  _js_expr=format.format_state_name(cls.get_full_name()) + "." + name,
950
1015
  _var_type=type_,
951
- _var_data=VarData.from_state(cls),
1016
+ _var_data=VarData.from_state(cls, name),
952
1017
  ).guess_type()
953
1018
 
954
1019
  # add the pydantic field dynamically (must be done before _init_var)
@@ -974,10 +1039,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
974
1039
  Args:
975
1040
  prop: The var instance to set.
976
1041
  """
977
- acutal_var_name = (
978
- prop._js_expr if "." not in prop._js_expr else prop._js_expr.split(".")[-1]
979
- )
980
- setattr(cls, acutal_var_name, prop)
1042
+ setattr(cls, prop._var_field_name, prop)
981
1043
 
982
1044
  @classmethod
983
1045
  def _create_event_handler(cls, fn):
@@ -1017,10 +1079,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1017
1079
  prop: The var to set the default value for.
1018
1080
  """
1019
1081
  # Get the pydantic field for the var.
1020
- if "." in prop._js_expr:
1021
- field = cls.get_fields()[prop._js_expr.split(".")[-1]]
1022
- else:
1023
- field = cls.get_fields()[prop._js_expr]
1082
+ field = cls.get_fields()[prop._var_field_name]
1024
1083
  if field.required:
1025
1084
  default_value = prop.get_default_value()
1026
1085
  if default_value is not None:
@@ -1302,7 +1361,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1302
1361
  for part1, part2 in zip(
1303
1362
  cls.get_full_name().split("."),
1304
1363
  other.get_full_name().split("."),
1305
- strict=False,
1306
1364
  ):
1307
1365
  if part1 != part2:
1308
1366
  break
@@ -1762,11 +1820,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1762
1820
  .union(self._always_dirty_computed_vars)
1763
1821
  )
1764
1822
 
1765
- subdelta = {
1766
- prop: getattr(self, prop)
1823
+ subdelta: Dict[str, Any] = {
1824
+ prop: self.get_value(getattr(self, prop))
1767
1825
  for prop in delta_vars
1768
1826
  if not types.is_backend_base_variable(prop, type(self))
1769
1827
  }
1828
+
1770
1829
  if len(subdelta) > 0:
1771
1830
  delta[self.get_full_name()] = subdelta
1772
1831
 
@@ -1775,9 +1834,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1775
1834
  for substate in self.dirty_substates.union(self._always_dirty_substates):
1776
1835
  delta.update(substates[substate].get_delta())
1777
1836
 
1778
- # Format the delta.
1779
- delta = format.format_state(delta)
1780
-
1781
1837
  # Return the delta.
1782
1838
  return delta
1783
1839
 
@@ -2418,7 +2474,7 @@ class StateUpdate:
2418
2474
  Returns:
2419
2475
  The state update as a JSON string.
2420
2476
  """
2421
- return format.json_dumps(dataclasses.asdict(self))
2477
+ return format.json_dumps(self)
2422
2478
 
2423
2479
 
2424
2480
  class StateManager(Base, ABC):
@@ -2577,16 +2633,24 @@ def _serialize_type(type_: Any) -> str:
2577
2633
  return f"{type_.__module__}.{type_.__qualname__}"
2578
2634
 
2579
2635
 
2636
+ def is_serializable(value: Any) -> bool:
2637
+ """Check if a value is serializable.
2638
+
2639
+ Args:
2640
+ value: The value to check.
2641
+
2642
+ Returns:
2643
+ Whether the value is serializable.
2644
+ """
2645
+ try:
2646
+ return bool(dill.dumps(value))
2647
+ except Exception:
2648
+ return False
2649
+
2650
+
2580
2651
  def state_to_schema(
2581
2652
  state: BaseState,
2582
- ) -> List[
2583
- Tuple[
2584
- str,
2585
- str,
2586
- Any,
2587
- Union[bool, None],
2588
- ]
2589
- ]:
2653
+ ) -> List[Tuple[str, str, Any, Union[bool, None], Any]]:
2590
2654
  """Convert a state to a schema.
2591
2655
 
2592
2656
  Args:
@@ -2606,6 +2670,7 @@ def state_to_schema(
2606
2670
  if isinstance(model_field.required, bool)
2607
2671
  else None
2608
2672
  ),
2673
+ (model_field.default if is_serializable(model_field.default) else None),
2609
2674
  )
2610
2675
  for field_name, model_field in state.__fields__.items()
2611
2676
  )
@@ -2690,7 +2755,9 @@ class StateManagerDisk(StateManager):
2690
2755
  Returns:
2691
2756
  The path for the token.
2692
2757
  """
2693
- return (self.states_directory / f"{token}.pkl").absolute()
2758
+ return (
2759
+ self.states_directory / f"{md5(token.encode()).hexdigest()}.pkl"
2760
+ ).absolute()
2694
2761
 
2695
2762
  async def load_state(self, token: str, root_state: BaseState) -> BaseState:
2696
2763
  """Load a state object based on the provided token.
@@ -3634,22 +3701,16 @@ class MutableProxy(wrapt.ObjectProxy):
3634
3701
 
3635
3702
 
3636
3703
  @serializer
3637
- def serialize_mutable_proxy(mp: MutableProxy) -> SerializedType:
3638
- """Serialize the wrapped value of a MutableProxy.
3704
+ def serialize_mutable_proxy(mp: MutableProxy):
3705
+ """Return the wrapped value of a MutableProxy.
3639
3706
 
3640
3707
  Args:
3641
3708
  mp: The MutableProxy to serialize.
3642
3709
 
3643
3710
  Returns:
3644
- The serialized wrapped object.
3645
-
3646
- Raises:
3647
- ValueError: when the wrapped object is not serializable.
3711
+ The wrapped object.
3648
3712
  """
3649
- value = serialize(mp.__wrapped__)
3650
- if value is None:
3651
- raise ValueError(f"Cannot serialize {type(mp.__wrapped__)}")
3652
- return value
3713
+ return mp.__wrapped__
3653
3714
 
3654
3715
 
3655
3716
  class ImmutableMutableProxy(MutableProxy):
reflex/style.py CHANGED
@@ -13,6 +13,7 @@ from reflex.utils.imports import ImportVar
13
13
  from reflex.vars import VarData
14
14
  from reflex.vars.base import CallableVar, LiteralVar, Var
15
15
  from reflex.vars.function import FunctionVar
16
+ from reflex.vars.object import ObjectVar
16
17
 
17
18
  SYSTEM_COLOR_MODE: str = "system"
18
19
  LIGHT_COLOR_MODE: str = "light"
@@ -188,7 +189,16 @@ def convert(
188
189
  out[k] = return_value
189
190
 
190
191
  for key, value in style_dict.items():
191
- keys = format_style_key(key)
192
+ keys = (
193
+ format_style_key(key)
194
+ if not isinstance(value, (dict, ObjectVar))
195
+ or (
196
+ isinstance(value, Breakpoints)
197
+ and all(not isinstance(v, dict) for v in value.values())
198
+ )
199
+ else (key,)
200
+ )
201
+
192
202
  if isinstance(value, Var):
193
203
  return_val = value
194
204
  new_var_data = value._get_all_var_data()
@@ -283,7 +293,7 @@ def _format_emotion_style_pseudo_selector(key: str) -> str:
283
293
  """Format a pseudo selector for emotion CSS-in-JS.
284
294
 
285
295
  Args:
286
- key: Underscore-prefixed or colon-prefixed pseudo selector key (_hover).
296
+ key: Underscore-prefixed or colon-prefixed pseudo selector key (_hover/:hover).
287
297
 
288
298
  Returns:
289
299
  A self-referential pseudo selector key (&:hover).