reflex 0.6.3a4__py3-none-any.whl → 0.6.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 (96) hide show
  1. reflex/.templates/jinja/web/pages/_app.js.jinja2 +2 -2
  2. reflex/.templates/jinja/web/utils/context.js.jinja2 +3 -1
  3. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -3
  4. reflex/.templates/web/components/shiki/code.js +29 -0
  5. reflex/.templates/web/jsconfig.json +2 -1
  6. reflex/.templates/web/utils/state.js +6 -4
  7. reflex/__init__.py +6 -3
  8. reflex/__init__.pyi +4 -3
  9. reflex/app.py +6 -5
  10. reflex/compiler/compiler.py +6 -7
  11. reflex/compiler/utils.py +8 -1
  12. reflex/components/base/error_boundary.py +37 -24
  13. reflex/components/base/error_boundary.pyi +8 -7
  14. reflex/components/component.py +9 -4
  15. reflex/components/core/banner.py +2 -2
  16. reflex/components/core/client_side_routing.py +1 -1
  17. reflex/components/core/clipboard.py +1 -1
  18. reflex/components/core/clipboard.pyi +1 -1
  19. reflex/components/core/cond.py +1 -1
  20. reflex/components/core/debounce.py +5 -1
  21. reflex/components/core/upload.py +7 -9
  22. reflex/components/core/upload.pyi +2 -2
  23. reflex/components/datadisplay/code.py +1 -1
  24. reflex/components/datadisplay/dataeditor.py +83 -18
  25. reflex/components/datadisplay/dataeditor.pyi +67 -15
  26. reflex/components/datadisplay/shiki_code_block.py +813 -0
  27. reflex/components/datadisplay/shiki_code_block.pyi +2211 -0
  28. reflex/components/dynamic.py +3 -3
  29. reflex/components/el/elements/forms.py +37 -23
  30. reflex/components/el/elements/forms.pyi +7 -4
  31. reflex/components/markdown/markdown.py +12 -1
  32. reflex/components/moment/moment.pyi +1 -1
  33. reflex/components/radix/primitives/drawer.pyi +2 -2
  34. reflex/components/radix/themes/base.py +2 -2
  35. reflex/components/radix/themes/color_mode.pyi +1 -1
  36. reflex/components/radix/themes/components/alert_dialog.pyi +1 -1
  37. reflex/components/radix/themes/components/checkbox.pyi +3 -3
  38. reflex/components/radix/themes/components/context_menu.pyi +1 -1
  39. reflex/components/radix/themes/components/dialog.pyi +2 -2
  40. reflex/components/radix/themes/components/dropdown_menu.pyi +2 -2
  41. reflex/components/radix/themes/components/hover_card.pyi +2 -2
  42. reflex/components/radix/themes/components/popover.pyi +1 -1
  43. reflex/components/radix/themes/components/radio_cards.pyi +1 -1
  44. reflex/components/radix/themes/components/radio_group.pyi +1 -1
  45. reflex/components/radix/themes/components/select.pyi +6 -6
  46. reflex/components/radix/themes/components/switch.pyi +1 -1
  47. reflex/components/radix/themes/components/tabs.pyi +2 -2
  48. reflex/components/radix/themes/components/tooltip.pyi +4 -2
  49. reflex/components/react_player/__init__.py +1 -0
  50. reflex/components/react_player/audio.pyi +6 -3
  51. reflex/components/react_player/react_player.py +12 -1
  52. reflex/components/react_player/react_player.pyi +11 -3
  53. reflex/components/react_player/video.pyi +6 -3
  54. reflex/components/recharts/recharts.py +2 -2
  55. reflex/components/sonner/toast.py +1 -1
  56. reflex/components/suneditor/editor.py +40 -16
  57. reflex/components/suneditor/editor.pyi +15 -11
  58. reflex/config.py +284 -20
  59. reflex/constants/__init__.py +2 -0
  60. reflex/constants/base.py +53 -31
  61. reflex/constants/compiler.py +2 -12
  62. reflex/constants/config.py +1 -2
  63. reflex/constants/installer.py +88 -32
  64. reflex/constants/style.py +1 -1
  65. reflex/constants/utils.py +32 -0
  66. reflex/custom_components/custom_components.py +3 -3
  67. reflex/event.py +152 -84
  68. reflex/experimental/__init__.py +2 -0
  69. reflex/experimental/client_state.py +1 -1
  70. reflex/experimental/layout.pyi +1 -1
  71. reflex/istate/storage.py +144 -0
  72. reflex/model.py +8 -11
  73. reflex/reflex.py +18 -17
  74. reflex/state.py +89 -151
  75. reflex/style.py +1 -1
  76. reflex/testing.py +2 -1
  77. reflex/utils/build.py +0 -12
  78. reflex/utils/exceptions.py +8 -0
  79. reflex/utils/exec.py +22 -4
  80. reflex/utils/imports.py +6 -0
  81. reflex/utils/net.py +2 -4
  82. reflex/utils/path_ops.py +7 -21
  83. reflex/utils/prerequisites.py +11 -17
  84. reflex/utils/pyi_generator.py +91 -3
  85. reflex/utils/registry.py +2 -6
  86. reflex/utils/types.py +14 -0
  87. reflex/vars/base.py +453 -424
  88. reflex/vars/function.py +6 -16
  89. reflex/vars/number.py +46 -67
  90. reflex/vars/object.py +1 -31
  91. reflex/vars/sequence.py +177 -47
  92. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/METADATA +1 -1
  93. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/RECORD +96 -91
  94. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/LICENSE +0 -0
  95. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/WHEEL +0 -0
  96. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,144 @@
1
+ """Client-side storage classes for reflex state variables."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from reflex.utils import format
8
+
9
+
10
+ class ClientStorageBase:
11
+ """Base class for client-side storage."""
12
+
13
+ def options(self) -> dict[str, Any]:
14
+ """Get the options for the storage.
15
+
16
+ Returns:
17
+ All set options for the storage (not None).
18
+ """
19
+ return {
20
+ format.to_camel_case(k): v for k, v in vars(self).items() if v is not None
21
+ }
22
+
23
+
24
+ class Cookie(ClientStorageBase, str):
25
+ """Represents a state Var that is stored as a cookie in the browser."""
26
+
27
+ name: str | None
28
+ path: str
29
+ max_age: int | None
30
+ domain: str | None
31
+ secure: bool | None
32
+ same_site: str
33
+
34
+ def __new__(
35
+ cls,
36
+ object: Any = "",
37
+ encoding: str | None = None,
38
+ errors: str | None = None,
39
+ /,
40
+ name: str | None = None,
41
+ path: str = "/",
42
+ max_age: int | None = None,
43
+ domain: str | None = None,
44
+ secure: bool | None = None,
45
+ same_site: str = "lax",
46
+ ):
47
+ """Create a client-side Cookie (str).
48
+
49
+ Args:
50
+ object: The initial object.
51
+ encoding: The encoding to use.
52
+ errors: The error handling scheme to use.
53
+ name: The name of the cookie on the client side.
54
+ path: Cookie path. Use / as the path if the cookie should be accessible on all pages.
55
+ max_age: Relative max age of the cookie in seconds from when the client receives it.
56
+ domain: Domain for the cookie (sub.domain.com or .allsubdomains.com).
57
+ secure: Is the cookie only accessible through HTTPS?
58
+ same_site: Whether the cookie is sent with third party requests.
59
+ One of (true|false|none|lax|strict)
60
+
61
+ Returns:
62
+ The client-side Cookie object.
63
+
64
+ Note: expires (absolute Date) is not supported at this time.
65
+ """
66
+ if encoding or errors:
67
+ inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
68
+ else:
69
+ inst = super().__new__(cls, object)
70
+ inst.name = name
71
+ inst.path = path
72
+ inst.max_age = max_age
73
+ inst.domain = domain
74
+ inst.secure = secure
75
+ inst.same_site = same_site
76
+ return inst
77
+
78
+
79
+ class LocalStorage(ClientStorageBase, str):
80
+ """Represents a state Var that is stored in localStorage in the browser."""
81
+
82
+ name: str | None
83
+ sync: bool = False
84
+
85
+ def __new__(
86
+ cls,
87
+ object: Any = "",
88
+ encoding: str | None = None,
89
+ errors: str | None = None,
90
+ /,
91
+ name: str | None = None,
92
+ sync: bool = False,
93
+ ) -> "LocalStorage":
94
+ """Create a client-side localStorage (str).
95
+
96
+ Args:
97
+ object: The initial object.
98
+ encoding: The encoding to use.
99
+ errors: The error handling scheme to use.
100
+ name: The name of the storage key on the client side.
101
+ sync: Whether changes should be propagated to other tabs.
102
+
103
+ Returns:
104
+ The client-side localStorage object.
105
+ """
106
+ if encoding or errors:
107
+ inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
108
+ else:
109
+ inst = super().__new__(cls, object)
110
+ inst.name = name
111
+ inst.sync = sync
112
+ return inst
113
+
114
+
115
+ class SessionStorage(ClientStorageBase, str):
116
+ """Represents a state Var that is stored in sessionStorage in the browser."""
117
+
118
+ name: str | None
119
+
120
+ def __new__(
121
+ cls,
122
+ object: Any = "",
123
+ encoding: str | None = None,
124
+ errors: str | None = None,
125
+ /,
126
+ name: str | None = None,
127
+ ) -> "SessionStorage":
128
+ """Create a client-side sessionStorage (str).
129
+
130
+ Args:
131
+ object: The initial object.
132
+ encoding: The encoding to use.
133
+ errors: The error handling scheme to use
134
+ name: The name of the storage on the client side
135
+
136
+ Returns:
137
+ The client-side sessionStorage object.
138
+ """
139
+ if encoding or errors:
140
+ inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
141
+ else:
142
+ inst = super().__new__(cls, object)
143
+ inst.name = name
144
+ return inst
reflex/model.py CHANGED
@@ -2,9 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import os
6
5
  from collections import defaultdict
7
- from pathlib import Path
8
6
  from typing import Any, ClassVar, Optional, Type, Union
9
7
 
10
8
  import alembic.autogenerate
@@ -18,9 +16,8 @@ import sqlalchemy
18
16
  import sqlalchemy.exc
19
17
  import sqlalchemy.orm
20
18
 
21
- from reflex import constants
22
19
  from reflex.base import Base
23
- from reflex.config import get_config
20
+ from reflex.config import environment, get_config
24
21
  from reflex.utils import console
25
22
  from reflex.utils.compat import sqlmodel, sqlmodel_field_has_primary_key
26
23
 
@@ -41,12 +38,12 @@ def get_engine(url: str | None = None) -> sqlalchemy.engine.Engine:
41
38
  url = url or conf.db_url
42
39
  if url is None:
43
40
  raise ValueError("No database url configured")
44
- if not Path(constants.ALEMBIC_CONFIG).exists():
41
+ if not environment.ALEMBIC_CONFIG.exists():
45
42
  console.warn(
46
43
  "Database is not initialized, run [bold]reflex db init[/bold] first."
47
44
  )
48
45
  # Print the SQL queries if the log level is INFO or lower.
49
- echo_db_query = os.environ.get("SQLALCHEMY_ECHO") == "True"
46
+ echo_db_query = environment.SQLALCHEMY_ECHO
50
47
  # Needed for the admin dash on sqlite.
51
48
  connect_args = {"check_same_thread": False} if url.startswith("sqlite") else {}
52
49
  return sqlmodel.create_engine(url, echo=echo_db_query, connect_args=connect_args)
@@ -234,7 +231,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
234
231
  Returns:
235
232
  tuple of (config, script_directory)
236
233
  """
237
- config = alembic.config.Config(constants.ALEMBIC_CONFIG)
234
+ config = alembic.config.Config(environment.ALEMBIC_CONFIG)
238
235
  return config, alembic.script.ScriptDirectory(
239
236
  config.get_main_option("script_location", default="version"),
240
237
  )
@@ -269,8 +266,8 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
269
266
  def alembic_init(cls):
270
267
  """Initialize alembic for the project."""
271
268
  alembic.command.init(
272
- config=alembic.config.Config(constants.ALEMBIC_CONFIG),
273
- directory=str(Path(constants.ALEMBIC_CONFIG).parent / "alembic"),
269
+ config=alembic.config.Config(environment.ALEMBIC_CONFIG),
270
+ directory=str(environment.ALEMBIC_CONFIG.parent / "alembic"),
274
271
  )
275
272
 
276
273
  @classmethod
@@ -290,7 +287,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
290
287
  Returns:
291
288
  True when changes have been detected.
292
289
  """
293
- if not Path(constants.ALEMBIC_CONFIG).exists():
290
+ if not environment.ALEMBIC_CONFIG.exists():
294
291
  return False
295
292
 
296
293
  config, script_directory = cls._alembic_config()
@@ -391,7 +388,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
391
388
  True - indicating the process was successful.
392
389
  None - indicating the process was skipped.
393
390
  """
394
- if not Path(constants.ALEMBIC_CONFIG).exists():
391
+ if not environment.ALEMBIC_CONFIG.exists():
395
392
  return
396
393
 
397
394
  with cls.get_db_engine().connect() as connection:
reflex/reflex.py CHANGED
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import atexit
6
6
  import os
7
- import webbrowser
8
7
  from pathlib import Path
9
8
  from typing import List, Optional
10
9
 
@@ -14,7 +13,7 @@ from reflex_cli.deployments import deployments_cli
14
13
  from reflex_cli.utils import dependency
15
14
 
16
15
  from reflex import constants
17
- from reflex.config import get_config
16
+ from reflex.config import environment, get_config
18
17
  from reflex.custom_components.custom_components import custom_components_cli
19
18
  from reflex.state import reset_disk_state_manager
20
19
  from reflex.utils import console, redir, telemetry
@@ -275,9 +274,17 @@ def run(
275
274
  constants.Env.DEV, help="The environment to run the app in."
276
275
  ),
277
276
  frontend: bool = typer.Option(
278
- False, "--frontend-only", help="Execute only frontend."
277
+ False,
278
+ "--frontend-only",
279
+ help="Execute only frontend.",
280
+ envvar=constants.ENV_FRONTEND_ONLY_ENV_VAR,
281
+ ),
282
+ backend: bool = typer.Option(
283
+ False,
284
+ "--backend-only",
285
+ help="Execute only backend.",
286
+ envvar=constants.ENV_BACKEND_ONLY_ENV_VAR,
279
287
  ),
280
- backend: bool = typer.Option(False, "--backend-only", help="Execute only backend."),
281
288
  frontend_port: str = typer.Option(
282
289
  config.frontend_port, help="Specify a different frontend port."
283
290
  ),
@@ -292,6 +299,12 @@ def run(
292
299
  ),
293
300
  ):
294
301
  """Run the app in the current directory."""
302
+ if frontend and backend:
303
+ console.error("Cannot use both --frontend-only and --backend-only options.")
304
+ raise typer.Exit(1)
305
+ os.environ[constants.ENV_BACKEND_ONLY_ENV_VAR] = str(backend).lower()
306
+ os.environ[constants.ENV_FRONTEND_ONLY_ENV_VAR] = str(frontend).lower()
307
+
295
308
  _run(env, frontend, backend, frontend_port, backend_port, backend_host, loglevel)
296
309
 
297
310
 
@@ -407,7 +420,7 @@ def db_init():
407
420
  return
408
421
 
409
422
  # Check the alembic config.
410
- if Path(constants.ALEMBIC_CONFIG).exists():
423
+ if environment.ALEMBIC_CONFIG.exists():
411
424
  console.error(
412
425
  "Database is already initialized. Use "
413
426
  "[bold]reflex db makemigrations[/bold] to create schema change "
@@ -586,18 +599,6 @@ def deploy(
586
599
  )
587
600
 
588
601
 
589
- @cli.command()
590
- def demo(
591
- frontend_port: str = typer.Option(
592
- "3001", help="Specify a different frontend port."
593
- ),
594
- backend_port: str = typer.Option("8001", help="Specify a different backend port."),
595
- ):
596
- """Run the demo app."""
597
- # Open the demo app in a terminal.
598
- webbrowser.open("https://demo.reflex.run")
599
-
600
-
601
602
  cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
602
603
  cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")
603
604
  cli.add_typer(
reflex/state.py CHANGED
@@ -8,7 +8,6 @@ import copy
8
8
  import dataclasses
9
9
  import functools
10
10
  import inspect
11
- import os
12
11
  import pickle
13
12
  import sys
14
13
  import uuid
@@ -31,6 +30,7 @@ from typing import (
31
30
  Set,
32
31
  Tuple,
33
32
  Type,
33
+ TypeVar,
34
34
  Union,
35
35
  cast,
36
36
  get_args,
@@ -40,8 +40,12 @@ from typing import (
40
40
  from sqlalchemy.orm import DeclarativeBase
41
41
  from typing_extensions import Self
42
42
 
43
+ from reflex import event
43
44
  from reflex.config import get_config
44
45
  from reflex.istate.data import RouterData
46
+ from reflex.istate.storage import (
47
+ ClientStorageBase,
48
+ )
45
49
  from reflex.vars.base import (
46
50
  ComputedVar,
47
51
  DynamicRouteVar,
@@ -64,6 +68,7 @@ from redis.exceptions import ResponseError
64
68
  import reflex.istate.dynamic
65
69
  from reflex import constants
66
70
  from reflex.base import Base
71
+ from reflex.config import environment
67
72
  from reflex.event import (
68
73
  BACKGROUND_TASK_MARKER,
69
74
  Event,
@@ -75,6 +80,7 @@ from reflex.utils import console, format, path_ops, prerequisites, types
75
80
  from reflex.utils.exceptions import (
76
81
  ComputedVarShadowsBaseVars,
77
82
  ComputedVarShadowsStateVar,
83
+ DynamicComponentInvalidSignature,
78
84
  DynamicRouteArgShadowsStateVar,
79
85
  EventHandlerShadowsBuiltInStateMethod,
80
86
  ImmutableStateError,
@@ -214,6 +220,7 @@ class EventHandlerSetVar(EventHandler):
214
220
  Raises:
215
221
  AttributeError: If the given Var name does not exist on the state.
216
222
  EventHandlerValueError: If the given Var name is not a str
223
+ NotImplementedError: If the setter for the given Var is async
217
224
  """
218
225
  from reflex.utils.exceptions import EventHandlerValueError
219
226
 
@@ -222,11 +229,20 @@ class EventHandlerSetVar(EventHandler):
222
229
  raise EventHandlerValueError(
223
230
  f"Var name must be passed as a string, got {args[0]!r}"
224
231
  )
232
+
233
+ handler = getattr(self.state_cls, constants.SETTER_PREFIX + args[0], None)
234
+
225
235
  # Check that the requested Var setter exists on the State at compile time.
226
- if getattr(self.state_cls, constants.SETTER_PREFIX + args[0], None) is None:
236
+ if handler is None:
227
237
  raise AttributeError(
228
238
  f"Variable `{args[0]}` cannot be set on `{self.state_cls.get_full_name()}`"
229
239
  )
240
+
241
+ if asyncio.iscoroutinefunction(handler.fn):
242
+ raise NotImplementedError(
243
+ f"Setter for {args[0]} is async, which is not supported."
244
+ )
245
+
230
246
  return super().__call__(*args)
231
247
 
232
248
 
@@ -2047,12 +2063,24 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
2047
2063
  """
2048
2064
  try:
2049
2065
  return pickle.dumps((self._to_schema(), self))
2050
- except pickle.PicklingError:
2051
- console.warn(
2066
+ except (pickle.PicklingError, AttributeError) as og_pickle_error:
2067
+ error = (
2052
2068
  f"Failed to serialize state {self.get_full_name()} due to unpicklable object. "
2053
- "This state will not be persisted."
2069
+ "This state will not be persisted. "
2054
2070
  )
2055
- return b""
2071
+ try:
2072
+ import dill
2073
+
2074
+ return dill.dumps((self._to_schema(), self))
2075
+ except ImportError:
2076
+ error += (
2077
+ f"Pickle error: {og_pickle_error}. "
2078
+ "Consider `pip install 'dill>=0.3.8'` for more exotic serialization support."
2079
+ )
2080
+ except (pickle.PicklingError, TypeError, ValueError) as ex:
2081
+ error += f"Dill was also unable to pickle the state: {ex}"
2082
+ console.warn(error)
2083
+ return b""
2056
2084
 
2057
2085
  @classmethod
2058
2086
  def _deserialize(
@@ -2091,10 +2119,56 @@ class State(BaseState):
2091
2119
  is_hydrated: bool = False
2092
2120
 
2093
2121
 
2122
+ T = TypeVar("T", bound=BaseState)
2123
+
2124
+
2125
+ def dynamic(func: Callable[[T], Component]):
2126
+ """Create a dynamically generated components from a state class.
2127
+
2128
+ Args:
2129
+ func: The function to generate the component.
2130
+
2131
+ Returns:
2132
+ The dynamically generated component.
2133
+
2134
+ Raises:
2135
+ DynamicComponentInvalidSignature: If the function does not have exactly one parameter.
2136
+ DynamicComponentInvalidSignature: If the function does not have a type hint for the state class.
2137
+ """
2138
+ number_of_parameters = len(inspect.signature(func).parameters)
2139
+
2140
+ func_signature = get_type_hints(func)
2141
+
2142
+ if "return" in func_signature:
2143
+ func_signature.pop("return")
2144
+
2145
+ values = list(func_signature.values())
2146
+
2147
+ if number_of_parameters != 1:
2148
+ raise DynamicComponentInvalidSignature(
2149
+ "The function must have exactly one parameter, which is the state class."
2150
+ )
2151
+
2152
+ if len(values) != 1:
2153
+ raise DynamicComponentInvalidSignature(
2154
+ "You must provide a type hint for the state class in the function."
2155
+ )
2156
+
2157
+ state_class: Type[T] = values[0]
2158
+
2159
+ def wrapper() -> Component:
2160
+ from reflex.components.base.fragment import fragment
2161
+
2162
+ return fragment(state_class._evaluate(lambda state: func(state)))
2163
+
2164
+ return wrapper
2165
+
2166
+
2094
2167
  class FrontendEventExceptionState(State):
2095
2168
  """Substate for handling frontend exceptions."""
2096
2169
 
2097
- def handle_frontend_exception(self, stack: str) -> None:
2170
+ @event
2171
+ def handle_frontend_exception(self, stack: str, component_stack: str) -> None:
2098
2172
  """Handle frontend exceptions.
2099
2173
 
2100
2174
  If a frontend exception handler is provided, it will be called.
@@ -2102,6 +2176,7 @@ class FrontendEventExceptionState(State):
2102
2176
 
2103
2177
  Args:
2104
2178
  stack: The stack trace of the exception.
2179
+ component_stack: The stack trace of the component where the exception occurred.
2105
2180
 
2106
2181
  """
2107
2182
  app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
@@ -2842,9 +2917,13 @@ class StateManagerDisk(StateManager):
2842
2917
  for substate in state.get_substates():
2843
2918
  substate_token = _substate_key(client_token, substate)
2844
2919
 
2920
+ fresh_instance = await root_state.get_state(substate)
2845
2921
  instance = await self.load_state(substate_token)
2846
- if instance is None:
2847
- instance = await root_state.get_state(substate)
2922
+ if instance is not None:
2923
+ # Ensure all substates exist, even if they weren't serialized previously.
2924
+ instance.substates = fresh_instance.substates
2925
+ else:
2926
+ instance = fresh_instance
2848
2927
  state.substates[substate.get_name()] = instance
2849
2928
  instance.parent_state = state
2850
2929
 
@@ -3274,11 +3353,7 @@ class StateManagerRedis(StateManager):
3274
3353
  )
3275
3354
  except ResponseError:
3276
3355
  # Some redis servers only allow out-of-band configuration, so ignore errors here.
3277
- ignore_config_error = os.environ.get(
3278
- "REFLEX_IGNORE_REDIS_CONFIG_ERROR",
3279
- None,
3280
- )
3281
- if not ignore_config_error:
3356
+ if not environment.REFLEX_IGNORE_REDIS_CONFIG_ERROR:
3282
3357
  raise
3283
3358
  async with self.redis.pubsub() as pubsub:
3284
3359
  await pubsub.psubscribe(lock_key_channel)
@@ -3350,143 +3425,6 @@ def get_state_manager() -> StateManager:
3350
3425
  return app.state_manager
3351
3426
 
3352
3427
 
3353
- class ClientStorageBase:
3354
- """Base class for client-side storage."""
3355
-
3356
- def options(self) -> dict[str, Any]:
3357
- """Get the options for the storage.
3358
-
3359
- Returns:
3360
- All set options for the storage (not None).
3361
- """
3362
- return {
3363
- format.to_camel_case(k): v for k, v in vars(self).items() if v is not None
3364
- }
3365
-
3366
-
3367
- class Cookie(ClientStorageBase, str):
3368
- """Represents a state Var that is stored as a cookie in the browser."""
3369
-
3370
- name: str | None
3371
- path: str
3372
- max_age: int | None
3373
- domain: str | None
3374
- secure: bool | None
3375
- same_site: str
3376
-
3377
- def __new__(
3378
- cls,
3379
- object: Any = "",
3380
- encoding: str | None = None,
3381
- errors: str | None = None,
3382
- /,
3383
- name: str | None = None,
3384
- path: str = "/",
3385
- max_age: int | None = None,
3386
- domain: str | None = None,
3387
- secure: bool | None = None,
3388
- same_site: str = "lax",
3389
- ):
3390
- """Create a client-side Cookie (str).
3391
-
3392
- Args:
3393
- object: The initial object.
3394
- encoding: The encoding to use.
3395
- errors: The error handling scheme to use.
3396
- name: The name of the cookie on the client side.
3397
- path: Cookie path. Use / as the path if the cookie should be accessible on all pages.
3398
- max_age: Relative max age of the cookie in seconds from when the client receives it.
3399
- domain: Domain for the cookie (sub.domain.com or .allsubdomains.com).
3400
- secure: Is the cookie only accessible through HTTPS?
3401
- same_site: Whether the cookie is sent with third party requests.
3402
- One of (true|false|none|lax|strict)
3403
-
3404
- Returns:
3405
- The client-side Cookie object.
3406
-
3407
- Note: expires (absolute Date) is not supported at this time.
3408
- """
3409
- if encoding or errors:
3410
- inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
3411
- else:
3412
- inst = super().__new__(cls, object)
3413
- inst.name = name
3414
- inst.path = path
3415
- inst.max_age = max_age
3416
- inst.domain = domain
3417
- inst.secure = secure
3418
- inst.same_site = same_site
3419
- return inst
3420
-
3421
-
3422
- class LocalStorage(ClientStorageBase, str):
3423
- """Represents a state Var that is stored in localStorage in the browser."""
3424
-
3425
- name: str | None
3426
- sync: bool = False
3427
-
3428
- def __new__(
3429
- cls,
3430
- object: Any = "",
3431
- encoding: str | None = None,
3432
- errors: str | None = None,
3433
- /,
3434
- name: str | None = None,
3435
- sync: bool = False,
3436
- ) -> "LocalStorage":
3437
- """Create a client-side localStorage (str).
3438
-
3439
- Args:
3440
- object: The initial object.
3441
- encoding: The encoding to use.
3442
- errors: The error handling scheme to use.
3443
- name: The name of the storage key on the client side.
3444
- sync: Whether changes should be propagated to other tabs.
3445
-
3446
- Returns:
3447
- The client-side localStorage object.
3448
- """
3449
- if encoding or errors:
3450
- inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
3451
- else:
3452
- inst = super().__new__(cls, object)
3453
- inst.name = name
3454
- inst.sync = sync
3455
- return inst
3456
-
3457
-
3458
- class SessionStorage(ClientStorageBase, str):
3459
- """Represents a state Var that is stored in sessionStorage in the browser."""
3460
-
3461
- name: str | None
3462
-
3463
- def __new__(
3464
- cls,
3465
- object: Any = "",
3466
- encoding: str | None = None,
3467
- errors: str | None = None,
3468
- /,
3469
- name: str | None = None,
3470
- ) -> "SessionStorage":
3471
- """Create a client-side sessionStorage (str).
3472
-
3473
- Args:
3474
- object: The initial object.
3475
- encoding: The encoding to use.
3476
- errors: The error handling scheme to use
3477
- name: The name of the storage on the client side
3478
-
3479
- Returns:
3480
- The client-side sessionStorage object.
3481
- """
3482
- if encoding or errors:
3483
- inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
3484
- else:
3485
- inst = super().__new__(cls, object)
3486
- inst.name = name
3487
- return inst
3488
-
3489
-
3490
3428
  class MutableProxy(wrapt.ObjectProxy):
3491
3429
  """A proxy for a mutable object that tracks changes."""
3492
3430
 
reflex/style.py CHANGED
@@ -23,7 +23,7 @@ LiteralColorMode = Literal["system", "light", "dark"]
23
23
 
24
24
  # Reference the global ColorModeContext
25
25
  color_mode_imports = {
26
- f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="ColorModeContext")],
26
+ f"$/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="ColorModeContext")],
27
27
  "react": [ImportVar(tag="useContext")],
28
28
  }
29
29
 
reflex/testing.py CHANGED
@@ -249,7 +249,8 @@ class AppHarness:
249
249
  return textwrap.dedent(source)
250
250
 
251
251
  def _initialize_app(self):
252
- os.environ["TELEMETRY_ENABLED"] = "" # disable telemetry reporting for tests
252
+ # disable telemetry reporting for tests
253
+ os.environ["TELEMETRY_ENABLED"] = "false"
253
254
  self.app_path.mkdir(parents=True, exist_ok=True)
254
255
  if self.app_source is not None:
255
256
  app_globals = self._get_globals_from_signature(self.app_source)
reflex/utils/build.py CHANGED
@@ -23,18 +23,6 @@ def set_env_json():
23
23
  )
24
24
 
25
25
 
26
- def set_os_env(**kwargs):
27
- """Set os environment variables.
28
-
29
- Args:
30
- kwargs: env key word args.
31
- """
32
- for key, value in kwargs.items():
33
- if not value:
34
- continue
35
- os.environ[key.upper()] = value
36
-
37
-
38
26
  def generate_sitemap_config(deploy_url: str, export=False):
39
27
  """Generate the sitemap config file.
40
28
 
@@ -135,3 +135,11 @@ class SetUndefinedStateVarError(ReflexError, AttributeError):
135
135
 
136
136
  class StateSchemaMismatchError(ReflexError, TypeError):
137
137
  """Raised when the serialized schema of a state class does not match the current schema."""
138
+
139
+
140
+ class EnvironmentVarValueError(ReflexError, ValueError):
141
+ """Raised when an environment variable is set to an invalid value."""
142
+
143
+
144
+ class DynamicComponentInvalidSignature(ReflexError, TypeError):
145
+ """Raised when a dynamic component has an invalid signature."""