reflex 0.6.0a4__py3-none-any.whl → 0.6.1a1__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 (56) hide show
  1. reflex/.templates/jinja/web/pages/_app.js.jinja2 +14 -0
  2. reflex/.templates/web/utils/state.js +67 -40
  3. reflex/app.py +9 -5
  4. reflex/app_mixins/lifespan.py +24 -6
  5. reflex/base.py +7 -13
  6. reflex/compiler/utils.py +17 -8
  7. reflex/components/base/bare.py +3 -1
  8. reflex/components/base/meta.py +5 -3
  9. reflex/components/component.py +19 -19
  10. reflex/components/core/cond.py +4 -4
  11. reflex/components/datadisplay/__init__.py +0 -1
  12. reflex/components/datadisplay/__init__.pyi +0 -1
  13. reflex/components/datadisplay/code.py +93 -106
  14. reflex/components/datadisplay/code.pyi +710 -53
  15. reflex/components/datadisplay/logo.py +22 -20
  16. reflex/components/dynamic.py +157 -0
  17. reflex/components/el/elements/forms.py +4 -1
  18. reflex/components/gridjs/datatable.py +2 -1
  19. reflex/components/markdown/markdown.py +10 -6
  20. reflex/components/markdown/markdown.pyi +3 -0
  21. reflex/components/radix/themes/components/progress.py +22 -0
  22. reflex/components/radix/themes/components/progress.pyi +2 -0
  23. reflex/components/radix/themes/components/segmented_control.py +3 -0
  24. reflex/components/radix/themes/components/segmented_control.pyi +2 -0
  25. reflex/components/radix/themes/layout/stack.py +1 -1
  26. reflex/components/recharts/cartesian.py +1 -1
  27. reflex/components/tags/iter_tag.py +5 -1
  28. reflex/config.py +2 -2
  29. reflex/constants/base.py +4 -1
  30. reflex/constants/installer.py +8 -1
  31. reflex/event.py +61 -20
  32. reflex/experimental/assets.py +3 -1
  33. reflex/experimental/client_state.py +5 -1
  34. reflex/experimental/misc.py +5 -3
  35. reflex/middleware/hydrate_middleware.py +1 -2
  36. reflex/page.py +10 -3
  37. reflex/reflex.py +20 -3
  38. reflex/state.py +105 -43
  39. reflex/style.py +12 -2
  40. reflex/testing.py +8 -4
  41. reflex/utils/console.py +1 -1
  42. reflex/utils/exceptions.py +4 -0
  43. reflex/utils/exec.py +170 -18
  44. reflex/utils/format.py +6 -44
  45. reflex/utils/path_ops.py +36 -1
  46. reflex/utils/prerequisites.py +42 -14
  47. reflex/utils/serializers.py +7 -46
  48. reflex/utils/types.py +17 -2
  49. reflex/vars/base.py +303 -43
  50. reflex/vars/number.py +3 -0
  51. reflex/vars/sequence.py +43 -0
  52. {reflex-0.6.0a4.dist-info → reflex-0.6.1a1.dist-info}/METADATA +2 -3
  53. {reflex-0.6.0a4.dist-info → reflex-0.6.1a1.dist-info}/RECORD +56 -55
  54. {reflex-0.6.0a4.dist-info → reflex-0.6.1a1.dist-info}/LICENSE +0 -0
  55. {reflex-0.6.0a4.dist-info → reflex-0.6.1a1.dist-info}/WHEEL +0 -0
  56. {reflex-0.6.0a4.dist-info → reflex-0.6.1a1.dist-info}/entry_points.txt +0 -0
@@ -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
@@ -91,12 +91,16 @@ class ClientStateVar(Var):
91
91
  default: The default value of the variable.
92
92
  global_ref: Whether the state should be accessible in any Component and on the backend.
93
93
 
94
+ Raises:
95
+ ValueError: If the var_name is not a string.
96
+
94
97
  Returns:
95
98
  ClientStateVar
96
99
  """
97
100
  if var_name is None:
98
101
  var_name = get_unique_variable_name()
99
- 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.")
100
104
  if default is NoValue:
101
105
  default_var = Var(_js_expr="")
102
106
  elif not isinstance(default, Var):
@@ -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:
@@ -1761,11 +1820,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1761
1820
  .union(self._always_dirty_computed_vars)
1762
1821
  )
1763
1822
 
1764
- subdelta = {
1765
- prop: getattr(self, prop)
1823
+ subdelta: Dict[str, Any] = {
1824
+ prop: self.get_value(getattr(self, prop))
1766
1825
  for prop in delta_vars
1767
1826
  if not types.is_backend_base_variable(prop, type(self))
1768
1827
  }
1828
+
1769
1829
  if len(subdelta) > 0:
1770
1830
  delta[self.get_full_name()] = subdelta
1771
1831
 
@@ -1774,9 +1834,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1774
1834
  for substate in self.dirty_substates.union(self._always_dirty_substates):
1775
1835
  delta.update(substates[substate].get_delta())
1776
1836
 
1777
- # Format the delta.
1778
- delta = format.format_state(delta)
1779
-
1780
1837
  # Return the delta.
1781
1838
  return delta
1782
1839
 
@@ -2417,7 +2474,7 @@ class StateUpdate:
2417
2474
  Returns:
2418
2475
  The state update as a JSON string.
2419
2476
  """
2420
- return format.json_dumps(dataclasses.asdict(self))
2477
+ return format.json_dumps(self)
2421
2478
 
2422
2479
 
2423
2480
  class StateManager(Base, ABC):
@@ -2576,16 +2633,24 @@ def _serialize_type(type_: Any) -> str:
2576
2633
  return f"{type_.__module__}.{type_.__qualname__}"
2577
2634
 
2578
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
+
2579
2651
  def state_to_schema(
2580
2652
  state: BaseState,
2581
- ) -> List[
2582
- Tuple[
2583
- str,
2584
- str,
2585
- Any,
2586
- Union[bool, None],
2587
- ]
2588
- ]:
2653
+ ) -> List[Tuple[str, str, Any, Union[bool, None], Any]]:
2589
2654
  """Convert a state to a schema.
2590
2655
 
2591
2656
  Args:
@@ -2605,6 +2670,7 @@ def state_to_schema(
2605
2670
  if isinstance(model_field.required, bool)
2606
2671
  else None
2607
2672
  ),
2673
+ (model_field.default if is_serializable(model_field.default) else None),
2608
2674
  )
2609
2675
  for field_name, model_field in state.__fields__.items()
2610
2676
  )
@@ -2689,7 +2755,9 @@ class StateManagerDisk(StateManager):
2689
2755
  Returns:
2690
2756
  The path for the token.
2691
2757
  """
2692
- return (self.states_directory / f"{token}.pkl").absolute()
2758
+ return (
2759
+ self.states_directory / f"{md5(token.encode()).hexdigest()}.pkl"
2760
+ ).absolute()
2693
2761
 
2694
2762
  async def load_state(self, token: str, root_state: BaseState) -> BaseState:
2695
2763
  """Load a state object based on the provided token.
@@ -3633,22 +3701,16 @@ class MutableProxy(wrapt.ObjectProxy):
3633
3701
 
3634
3702
 
3635
3703
  @serializer
3636
- def serialize_mutable_proxy(mp: MutableProxy) -> SerializedType:
3637
- """Serialize the wrapped value of a MutableProxy.
3704
+ def serialize_mutable_proxy(mp: MutableProxy):
3705
+ """Return the wrapped value of a MutableProxy.
3638
3706
 
3639
3707
  Args:
3640
3708
  mp: The MutableProxy to serialize.
3641
3709
 
3642
3710
  Returns:
3643
- The serialized wrapped object.
3644
-
3645
- Raises:
3646
- ValueError: when the wrapped object is not serializable.
3711
+ The wrapped object.
3647
3712
  """
3648
- value = serialize(mp.__wrapped__)
3649
- if value is None:
3650
- raise ValueError(f"Cannot serialize {type(mp.__wrapped__)}")
3651
- return value
3713
+ return mp.__wrapped__
3652
3714
 
3653
3715
 
3654
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).
reflex/testing.py CHANGED
@@ -340,6 +340,9 @@ class AppHarness:
340
340
 
341
341
  This is necessary when the backend is restarted and the state manager is a
342
342
  StateManagerRedis instance.
343
+
344
+ Raises:
345
+ RuntimeError: when the state manager cannot be reset
343
346
  """
344
347
  if (
345
348
  self.app_instance is not None
@@ -354,7 +357,8 @@ class AppHarness:
354
357
  self.app_instance._state_manager = StateManagerRedis.create(
355
358
  state=self.app_instance.state,
356
359
  )
357
- assert isinstance(self.app_instance.state_manager, StateManagerRedis)
360
+ if not isinstance(self.app_instance.state_manager, StateManagerRedis):
361
+ raise RuntimeError("Failed to reset state manager.")
358
362
 
359
363
  def _start_frontend(self):
360
364
  # Set up the frontend.
@@ -787,13 +791,13 @@ class AppHarness:
787
791
  Raises:
788
792
  RuntimeError: when the app hasn't started running
789
793
  TimeoutError: when the timeout expires before any states are seen
794
+ ValueError: when the state_manager is not a memory state manager
790
795
  """
791
796
  if self.app_instance is None:
792
797
  raise RuntimeError("App is not running.")
793
798
  state_manager = self.app_instance.state_manager
794
- assert isinstance(
795
- state_manager, (StateManagerMemory, StateManagerDisk)
796
- ), "Only works with memory state manager"
799
+ if not isinstance(state_manager, (StateManagerMemory, StateManagerDisk)):
800
+ raise ValueError("Only works with memory or disk state manager")
797
801
  if not self._poll_for(
798
802
  target=lambda: state_manager.states,
799
803
  timeout=timeout,
reflex/utils/console.py CHANGED
@@ -58,7 +58,7 @@ def debug(msg: str, **kwargs):
58
58
  kwargs: Keyword arguments to pass to the print function.
59
59
  """
60
60
  if is_debug():
61
- msg_ = f"[blue]Debug: {msg}[/blue]"
61
+ msg_ = f"[purple]Debug: {msg}[/purple]"
62
62
  if progress := kwargs.pop("progress", None):
63
63
  progress.console.print(msg_, **kwargs)
64
64
  else:
@@ -111,3 +111,7 @@ class GeneratedCodeHasNoFunctionDefs(ReflexError):
111
111
 
112
112
  class PrimitiveUnserializableToJSON(ReflexError, ValueError):
113
113
  """Raised when a primitive type is unserializable to JSON. Usually with NaN and Infinity."""
114
+
115
+
116
+ class InvalidLifespanTaskType(ReflexError, TypeError):
117
+ """Raised when an invalid task type is registered as a lifespan task."""