reflex 0.8.12a1__py3-none-any.whl → 0.8.12a2__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.

@@ -531,6 +531,7 @@ export const connect = async (
531
531
  ) => {
532
532
  // Get backend URL object from the endpoint.
533
533
  const endpoint = getBackendURL(EVENTURL);
534
+ const on_hydrated_queue = [];
534
535
 
535
536
  // Create the socket.
536
537
  socket.current = io(endpoint.href, {
@@ -552,7 +553,17 @@ export const connect = async (
552
553
 
553
554
  function checkVisibility() {
554
555
  if (document.visibilityState === "visible") {
555
- if (!socket.current.connected) {
556
+ if (!socket.current) {
557
+ connect(
558
+ socket,
559
+ dispatch,
560
+ transports,
561
+ setConnectErrors,
562
+ client_storage,
563
+ navigate,
564
+ params,
565
+ );
566
+ } else if (!socket.current.connected) {
556
567
  console.log("Socket is disconnected, attempting to reconnect ");
557
568
  socket.current.connect();
558
569
  } else {
@@ -593,6 +604,7 @@ export const connect = async (
593
604
 
594
605
  // When the socket disconnects reset the event_processing flag
595
606
  socket.current.on("disconnect", () => {
607
+ socket.current = null; // allow reconnect to occur automatically
596
608
  event_processing = false;
597
609
  window.removeEventListener("unload", disconnectTrigger);
598
610
  window.removeEventListener("beforeunload", disconnectTrigger);
@@ -603,6 +615,14 @@ export const connect = async (
603
615
  socket.current.on("event", async (update) => {
604
616
  for (const substate in update.delta) {
605
617
  dispatch[substate](update.delta[substate]);
618
+ // handle events waiting for `is_hydrated`
619
+ if (
620
+ substate === state_name &&
621
+ update.delta[substate]?.is_hydrated_rx_state_
622
+ ) {
623
+ queueEvents(on_hydrated_queue, socket, false, navigate, params);
624
+ on_hydrated_queue.length = 0;
625
+ }
606
626
  }
607
627
  applyClientStorageDelta(client_storage, update.delta);
608
628
  event_processing = !update.final;
@@ -612,7 +632,8 @@ export const connect = async (
612
632
  });
613
633
  socket.current.on("reload", async (event) => {
614
634
  event_processing = false;
615
- queueEvents([...initialEvents(), event], socket, true, navigate, params);
635
+ on_hydrated_queue.push(event);
636
+ queueEvents(initialEvents(), socket, true, navigate, params);
616
637
  });
617
638
  socket.current.on("new_token", async (new_token) => {
618
639
  token = new_token;
@@ -774,10 +795,32 @@ export const useEventLoop = (
774
795
  }
775
796
  }, [paramsR]);
776
797
 
798
+ const ensureSocketConnected = useCallback(async () => {
799
+ // only use websockets if state is present and backend is not disabled (reflex cloud).
800
+ if (
801
+ Object.keys(initialState).length > 1 &&
802
+ !isBackendDisabled() &&
803
+ !socket.current
804
+ ) {
805
+ // Initialize the websocket connection.
806
+ await connect(
807
+ socket,
808
+ dispatch,
809
+ ["websocket"],
810
+ setConnectErrors,
811
+ client_storage,
812
+ navigate,
813
+ () => params.current,
814
+ );
815
+ }
816
+ }, [socket, dispatch, setConnectErrors, client_storage, navigate, params]);
817
+
777
818
  // Function to add new events to the event queue.
778
819
  const addEvents = useCallback((events, args, event_actions) => {
779
820
  const _events = events.filter((e) => e !== undefined && e !== null);
780
821
 
822
+ ensureSocketConnected();
823
+
781
824
  if (!(args instanceof Array)) {
782
825
  args = [args];
783
826
  }
@@ -870,21 +913,8 @@ export const useEventLoop = (
870
913
 
871
914
  // Handle socket connect/disconnect.
872
915
  useEffect(() => {
873
- // only use websockets if state is present and backend is not disabled (reflex cloud).
874
- if (Object.keys(initialState).length > 1 && !isBackendDisabled()) {
875
- // Initialize the websocket connection.
876
- if (!socket.current) {
877
- connect(
878
- socket,
879
- dispatch,
880
- ["websocket"],
881
- setConnectErrors,
882
- client_storage,
883
- navigate,
884
- () => params.current,
885
- );
886
- }
887
- }
916
+ // Initialize the websocket connection.
917
+ ensureSocketConnected();
888
918
 
889
919
  // Cleanup function.
890
920
  return () => {
@@ -903,6 +933,7 @@ export const useEventLoop = (
903
933
  (async () => {
904
934
  // Process all outstanding events.
905
935
  while (event_queue.length > 0 && !event_processing) {
936
+ await ensureSocketConnected();
906
937
  await processEvent(socket.current, navigate, () => params.current);
907
938
  }
908
939
  })();
reflex/app.py CHANGED
@@ -97,6 +97,7 @@ from reflex.state import (
97
97
  State,
98
98
  StateManager,
99
99
  StateUpdate,
100
+ _split_substate_key,
100
101
  _substate_key,
101
102
  all_base_state_classes,
102
103
  code_uses_state_contexts,
@@ -1559,7 +1560,7 @@ class App(MiddlewareMixin, LifespanMixin):
1559
1560
  state._clean()
1560
1561
  await self.event_namespace.emit_update(
1561
1562
  update=StateUpdate(delta=delta),
1562
- sid=state.router.session.session_id,
1563
+ token=token,
1563
1564
  )
1564
1565
 
1565
1566
  def _process_background(
@@ -1599,7 +1600,7 @@ class App(MiddlewareMixin, LifespanMixin):
1599
1600
  # Send the update to the client.
1600
1601
  await self.event_namespace.emit_update(
1601
1602
  update=update,
1602
- sid=state.router.session.session_id,
1603
+ token=event.token,
1603
1604
  )
1604
1605
 
1605
1606
  task = asyncio.create_task(
@@ -2061,20 +2062,19 @@ class EventNamespace(AsyncNamespace):
2061
2062
  and console.error(f"Token cleanup error: {t.exception()}")
2062
2063
  )
2063
2064
 
2064
- async def emit_update(self, update: StateUpdate, sid: str) -> None:
2065
+ async def emit_update(self, update: StateUpdate, token: str) -> None:
2065
2066
  """Emit an update to the client.
2066
2067
 
2067
2068
  Args:
2068
2069
  update: The state update to send.
2069
- sid: The Socket.IO session id.
2070
+ token: The client token (tab) associated with the event.
2070
2071
  """
2071
- if not sid:
2072
+ client_token, _ = _split_substate_key(token)
2073
+ sid = self.token_to_sid.get(client_token)
2074
+ if sid is None:
2072
2075
  # If the sid is None, we are not connected to a client. Prevent sending
2073
2076
  # updates to all clients.
2074
- return
2075
- token = self.sid_to_token.get(sid)
2076
- if token is None:
2077
- console.warn(f"Attempting to send delta to disconnected websocket {sid}")
2077
+ console.warn(f"Attempting to send delta to disconnected client {token!r}")
2078
2078
  return
2079
2079
  # Creating a task prevents the update from being blocked behind other coroutines.
2080
2080
  await asyncio.create_task(
@@ -2165,7 +2165,7 @@ class EventNamespace(AsyncNamespace):
2165
2165
  # Process the events.
2166
2166
  async for update in updates_gen:
2167
2167
  # Emit the update from processing the event.
2168
- await self.emit_update(update=update, sid=sid)
2168
+ await self.emit_update(update=update, token=event.token)
2169
2169
 
2170
2170
  async def on_ping(self, sid: str):
2171
2171
  """Event for testing the API endpoint.
@@ -60,6 +60,17 @@ class LifespanMixin(AppMixin):
60
60
  for task in running_tasks:
61
61
  console.debug(f"Canceling lifespan task: {task}")
62
62
  task.cancel(msg="lifespan_cleanup")
63
+ # Disassociate sid / token pairings so they can be reconnected properly.
64
+ try:
65
+ event_namespace = self.event_namespace # pyright: ignore[reportAttributeAccessIssue]
66
+ except AttributeError:
67
+ pass
68
+ else:
69
+ try:
70
+ if event_namespace:
71
+ await event_namespace._token_manager.disconnect_all()
72
+ except Exception as e:
73
+ console.error(f"Error during lifespan cleanup: {e}")
63
74
 
64
75
  def register_lifespan_task(self, task: Callable | asyncio.Task, **task_kwargs):
65
76
  """Register a task to run during the lifespan of the app.
@@ -621,7 +621,10 @@ def purge_web_pages_dir():
621
621
  return
622
622
 
623
623
  # Empty out the web pages directory.
624
- utils.empty_dir(get_web_dir() / constants.Dirs.PAGES, keep_files=["routes.js"])
624
+ utils.empty_dir(
625
+ get_web_dir() / constants.Dirs.PAGES,
626
+ keep_files=["routes.js", "entry.client.js"],
627
+ )
625
628
 
626
629
 
627
630
  if TYPE_CHECKING:
@@ -17,6 +17,7 @@ from reflex.components.component import (
17
17
  from reflex.components.core.cond import cond
18
18
  from reflex.components.el.elements.forms import Input
19
19
  from reflex.components.radix.themes.layout.box import Box
20
+ from reflex.components.sonner.toast import toast
20
21
  from reflex.constants import Dirs
21
22
  from reflex.constants.compiler import Hooks, Imports
22
23
  from reflex.environment import environment
@@ -36,7 +37,8 @@ from reflex.utils.imports import ImportVar
36
37
  from reflex.vars import VarData
37
38
  from reflex.vars.base import Var, get_unique_variable_name
38
39
  from reflex.vars.function import FunctionVar
39
- from reflex.vars.sequence import LiteralStringVar
40
+ from reflex.vars.object import ObjectVar
41
+ from reflex.vars.sequence import ArrayVar, LiteralStringVar
40
42
 
41
43
  DEFAULT_UPLOAD_ID: str = "default"
42
44
 
@@ -178,6 +180,34 @@ def _on_drop_spec(files: Var) -> tuple[Var[Any]]:
178
180
  return (files,)
179
181
 
180
182
 
183
+ def _default_drop_rejected(rejected_files: ArrayVar[list[dict[str, Any]]]) -> EventSpec:
184
+ """Event handler for showing a toast with rejected file info.
185
+
186
+ Args:
187
+ rejected_files: The files that were rejected.
188
+
189
+ Returns:
190
+ An event spec that shows a toast with the rejected file info when triggered.
191
+ """
192
+
193
+ def _format_rejected_file_record(rf: ObjectVar[dict[str, Any]]) -> str:
194
+ rf = rf.to(ObjectVar, dict[str, dict[str, Any]])
195
+ file = rf["file"].to(ObjectVar, dict[str, Any])
196
+ errors = rf["errors"].to(ArrayVar, list[dict[str, Any]])
197
+ return (
198
+ f"{file['path']}: {errors.foreach(lambda err: err['message']).join(', ')}"
199
+ )
200
+
201
+ return toast.error(
202
+ title="Files not Accepted",
203
+ description=rejected_files.to(ArrayVar)
204
+ .foreach(_format_rejected_file_record)
205
+ .join("\n\n"),
206
+ close_button=True,
207
+ style={"white_space": "pre-line"},
208
+ )
209
+
210
+
181
211
  class UploadFilesProvider(Component):
182
212
  """AppWrap component that provides a dict of selected files by ID via useContext."""
183
213
 
@@ -191,6 +221,9 @@ class GhostUpload(Fragment):
191
221
  # Fired when files are dropped.
192
222
  on_drop: EventHandler[_on_drop_spec]
193
223
 
224
+ # Fired when dropped files do not meet the specified criteria.
225
+ on_drop_rejected: EventHandler[_on_drop_spec]
226
+
194
227
 
195
228
  class Upload(MemoizationLeaf):
196
229
  """A file upload component."""
@@ -234,6 +267,9 @@ class Upload(MemoizationLeaf):
234
267
  # Fired when files are dropped.
235
268
  on_drop: EventHandler[_on_drop_spec]
236
269
 
270
+ # Fired when dropped files do not meet the specified criteria.
271
+ on_drop_rejected: EventHandler[_on_drop_spec]
272
+
237
273
  # Style rules to apply when actively dragging.
238
274
  drag_active_style: Style | None = field(default=None, is_javascript_property=False)
239
275
 
@@ -295,6 +331,10 @@ class Upload(MemoizationLeaf):
295
331
  on_drop[ix] = event
296
332
  upload_props["on_drop"] = on_drop
297
333
 
334
+ if upload_props.get("on_drop_rejected") is None:
335
+ # If on_drop_rejected is not provided, show an error toast.
336
+ upload_props["on_drop_rejected"] = _default_drop_rejected
337
+
298
338
  input_props_unique_name = get_unique_variable_name()
299
339
  root_props_unique_name = get_unique_variable_name()
300
340
  is_drag_active_unique_name = get_unique_variable_name()
@@ -313,22 +353,22 @@ class Upload(MemoizationLeaf):
313
353
  ),
314
354
  )
315
355
 
316
- event_var, callback_str = StatefulComponent._get_memoized_event_triggers(
317
- GhostUpload.create(on_drop=upload_props["on_drop"])
318
- )["on_drop"]
319
-
320
- upload_props["on_drop"] = event_var
356
+ event_triggers = StatefulComponent._get_memoized_event_triggers(
357
+ GhostUpload.create(
358
+ on_drop=upload_props["on_drop"],
359
+ on_drop_rejected=upload_props["on_drop_rejected"],
360
+ )
361
+ )
362
+ callback_hooks = []
363
+ for trigger_name, (event_var, callback_str) in event_triggers.items():
364
+ upload_props[trigger_name] = event_var
365
+ callback_hooks.append(callback_str)
321
366
 
322
367
  upload_props = {
323
368
  format.to_camel_case(key): value for key, value in upload_props.items()
324
369
  }
325
370
 
326
- use_dropzone_arguments = Var.create(
327
- {
328
- "onDrop": event_var,
329
- **upload_props,
330
- }
331
- )
371
+ use_dropzone_arguments = Var.create(upload_props)
332
372
 
333
373
  left_side = (
334
374
  "const { "
@@ -344,11 +384,10 @@ class Upload(MemoizationLeaf):
344
384
  imports=Imports.EVENTS,
345
385
  hooks={Hooks.EVENTS: None},
346
386
  ),
347
- event_var._get_all_var_data(),
348
387
  use_dropzone_arguments._get_all_var_data(),
349
388
  VarData(
350
389
  hooks={
351
- callback_str: None,
390
+ **dict.fromkeys(callback_hooks, None),
352
391
  f"{left_side} = {right_side};": None,
353
392
  },
354
393
  imports={
reflex/istate/proxy.py CHANGED
@@ -71,10 +71,15 @@ class StateProxy(wrapt.ObjectProxy):
71
71
  state_instance: The state instance to proxy.
72
72
  parent_state_proxy: The parent state proxy, for linked mutability and context tracking.
73
73
  """
74
+ from reflex.state import _substate_key
75
+
74
76
  super().__init__(state_instance)
75
- # compile is not relevant to backend logic
76
77
  self._self_app = prerequisites.get_and_validate_app().app
77
78
  self._self_substate_path = tuple(state_instance.get_full_name().split("."))
79
+ self._self_substate_token = _substate_key(
80
+ state_instance.router.session.client_token,
81
+ self._self_substate_path,
82
+ )
78
83
  self._self_actx = None
79
84
  self._self_mutable = False
80
85
  self._self_actx_lock = asyncio.Lock()
@@ -127,16 +132,9 @@ class StateProxy(wrapt.ObjectProxy):
127
132
  msg = "The state is already mutable. Do not nest `async with self` blocks."
128
133
  raise ImmutableStateError(msg)
129
134
 
130
- from reflex.state import _substate_key
131
-
132
135
  await self._self_actx_lock.acquire()
133
136
  self._self_actx_lock_holder = current_task
134
- self._self_actx = self._self_app.modify_state(
135
- token=_substate_key(
136
- self.__wrapped__.router.session.client_token,
137
- self._self_substate_path,
138
- )
139
- )
137
+ self._self_actx = self._self_app.modify_state(token=self._self_substate_token)
140
138
  mutable_state = await self._self_actx.__aenter__()
141
139
  super().__setattr__(
142
140
  "__wrapped__", mutable_state.get_substate(self._self_substate_path)
@@ -378,17 +376,6 @@ class MutableProxy(wrapt.ObjectProxy):
378
376
  pydantic.BaseModel.__dict__
379
377
  )
380
378
 
381
- # These types will be wrapped in MutableProxy
382
- __mutable_types__ = (
383
- list,
384
- dict,
385
- set,
386
- Base,
387
- DeclarativeBase,
388
- BaseModelV2,
389
- BaseModelV1,
390
- )
391
-
392
379
  # Dynamically generated classes for tracking dataclass mutations.
393
380
  __dataclass_proxies__: dict[type, type] = {}
394
381
 
@@ -469,20 +456,6 @@ class MutableProxy(wrapt.ObjectProxy):
469
456
  return wrapped(*args, **(kwargs or {}))
470
457
  return None
471
458
 
472
- @classmethod
473
- def _is_mutable_type(cls, value: Any) -> bool:
474
- """Check if a value is of a mutable type and should be wrapped.
475
-
476
- Args:
477
- value: The value to check.
478
-
479
- Returns:
480
- Whether the value is of a mutable type.
481
- """
482
- return isinstance(value, cls.__mutable_types__) or (
483
- dataclasses.is_dataclass(value) and not isinstance(value, Var)
484
- )
485
-
486
459
  @staticmethod
487
460
  def _is_called_from_dataclasses_internal() -> bool:
488
461
  """Check if the current function is called from dataclasses helper.
@@ -514,7 +487,7 @@ class MutableProxy(wrapt.ObjectProxy):
514
487
  if self._is_called_from_dataclasses_internal():
515
488
  return value
516
489
  # Recursively wrap mutable types, but do not re-wrap MutableProxy instances.
517
- if self._is_mutable_type(value) and not isinstance(value, MutableProxy):
490
+ if is_mutable_type(type(value)) and not isinstance(value, MutableProxy):
518
491
  base_cls = globals()[self.__base_proxy__]
519
492
  return base_cls(
520
493
  wrapped=value,
@@ -575,7 +548,7 @@ class MutableProxy(wrapt.ObjectProxy):
575
548
  self._wrap_recursive_decorator,
576
549
  )
577
550
 
578
- if self._is_mutable_type(value) and __name not in (
551
+ if is_mutable_type(type(value)) and __name not in (
579
552
  "__wrapped__",
580
553
  "_self_state",
581
554
  "__dict__",
@@ -764,3 +737,30 @@ class ImmutableMutableProxy(MutableProxy):
764
737
  return super()._mark_dirty(
765
738
  wrapped=wrapped, instance=instance, args=args, kwargs=kwargs
766
739
  )
740
+
741
+
742
+ # These types will be wrapped in MutableProxy
743
+ MUTABLE_TYPES = (
744
+ list,
745
+ dict,
746
+ set,
747
+ Base,
748
+ DeclarativeBase,
749
+ BaseModelV2,
750
+ BaseModelV1,
751
+ )
752
+
753
+
754
+ @functools.lru_cache(maxsize=1024)
755
+ def is_mutable_type(type_: type) -> bool:
756
+ """Check if a type is mutable and should be wrapped.
757
+
758
+ Args:
759
+ type_: The type to check.
760
+
761
+ Returns:
762
+ Whether the type is mutable and should be wrapped.
763
+ """
764
+ return issubclass(type_, MUTABLE_TYPES) or (
765
+ dataclasses.is_dataclass(type_) and not issubclass(type_, Var)
766
+ )
reflex/reflex.py CHANGED
@@ -422,6 +422,13 @@ def compile(dry: bool, rich: bool):
422
422
  default=constants.Env.PROD.value,
423
423
  help="The environment to export the app in.",
424
424
  )
425
+ @click.option(
426
+ "--exclude-from-backend",
427
+ "backend_excluded_dirs",
428
+ multiple=True,
429
+ type=click.Path(exists=True, path_type=Path, resolve_path=True),
430
+ help="Files or directories to exclude from the backend zip. Can be used multiple times.",
431
+ )
425
432
  def export(
426
433
  zip: bool,
427
434
  frontend_only: bool,
@@ -429,6 +436,7 @@ def export(
429
436
  zip_dest_dir: str,
430
437
  upload_db_file: bool,
431
438
  env: LITERAL_ENV,
439
+ backend_excluded_dirs: tuple[Path, ...] = (),
432
440
  ):
433
441
  """Export the app to a zip file."""
434
442
  from reflex.utils import export as export_utils
@@ -455,6 +463,7 @@ def export(
455
463
  upload_db_file=upload_db_file,
456
464
  env=constants.Env.DEV if env == constants.Env.DEV else constants.Env.PROD,
457
465
  loglevel=config.loglevel.subprocess_level(),
466
+ backend_excluded_dirs=backend_excluded_dirs,
458
467
  )
459
468
 
460
469
 
@@ -660,6 +669,13 @@ def makemigrations(message: str | None):
660
669
  "--config",
661
670
  help="path to the config file",
662
671
  )
672
+ @click.option(
673
+ "--exclude-from-backend",
674
+ "backend_excluded_dirs",
675
+ multiple=True,
676
+ type=click.Path(exists=True, path_type=Path, resolve_path=True),
677
+ help="Files or directories to exclude from the backend zip. Can be used multiple times.",
678
+ )
663
679
  def deploy(
664
680
  app_name: str | None,
665
681
  app_id: str | None,
@@ -673,6 +689,7 @@ def deploy(
673
689
  project_name: str | None,
674
690
  token: str | None,
675
691
  config_path: str | None,
692
+ backend_excluded_dirs: tuple[Path, ...] = (),
676
693
  ):
677
694
  """Deploy the app to the Reflex hosting service."""
678
695
  from reflex_cli.utils import dependency
@@ -721,6 +738,7 @@ def deploy(
721
738
  zipping=zipping,
722
739
  loglevel=config.loglevel.subprocess_level(),
723
740
  upload_db_file=upload_db,
741
+ backend_excluded_dirs=backend_excluded_dirs,
724
742
  )
725
743
  ),
726
744
  regions=list(region),
reflex/state.py CHANGED
@@ -41,7 +41,7 @@ from reflex.event import (
41
41
  from reflex.istate import HANDLED_PICKLE_ERRORS, debug_failed_pickles
42
42
  from reflex.istate.data import RouterData
43
43
  from reflex.istate.proxy import ImmutableMutableProxy as ImmutableMutableProxy
44
- from reflex.istate.proxy import MutableProxy, StateProxy
44
+ from reflex.istate.proxy import MutableProxy, StateProxy, is_mutable_type
45
45
  from reflex.istate.storage import ClientStorageBase
46
46
  from reflex.model import Model
47
47
  from reflex.utils import console, format, prerequisites, types
@@ -1359,7 +1359,7 @@ class BaseState(EvenMoreBasicBaseState):
1359
1359
  if parent_state is not None:
1360
1360
  return getattr(parent_state, name)
1361
1361
 
1362
- if MutableProxy._is_mutable_type(value) and (
1362
+ if is_mutable_type(type(value)) and (
1363
1363
  name in super().__getattribute__("base_vars") or name in backend_vars
1364
1364
  ):
1365
1365
  # track changes in mutable containers (list, dict, set, etc)
reflex/testing.py CHANGED
@@ -47,6 +47,7 @@ from reflex.state import (
47
47
  )
48
48
  from reflex.utils import console, js_runtimes
49
49
  from reflex.utils.export import export
50
+ from reflex.utils.token_manager import TokenManager
50
51
  from reflex.utils.types import ASGIApp
51
52
 
52
53
  try:
@@ -774,6 +775,19 @@ class AppHarness:
774
775
  self.app_instance._state_manager = app_state_manager
775
776
  await self.state_manager.close()
776
777
 
778
+ def token_manager(self) -> TokenManager:
779
+ """Get the token manager for the app instance.
780
+
781
+ Returns:
782
+ The current token_manager attached to the app's EventNamespace.
783
+ """
784
+ assert self.app_instance is not None
785
+ app_event_namespace = self.app_instance.event_namespace
786
+ assert app_event_namespace is not None
787
+ app_token_manager = app_event_namespace._token_manager
788
+ assert app_token_manager is not None
789
+ return app_token_manager
790
+
777
791
  def poll_for_content(
778
792
  self,
779
793
  element: WebElement,
reflex/utils/build.py CHANGED
@@ -26,14 +26,14 @@ def set_env_json():
26
26
 
27
27
 
28
28
  def _zip(
29
+ *,
29
30
  component_name: constants.ComponentName,
30
- target: str | Path,
31
- root_dir: str | Path,
32
- exclude_venv_dirs: bool,
33
- upload_db_file: bool = False,
34
- dirs_to_exclude: set[str] | None = None,
35
- files_to_exclude: set[str] | None = None,
36
- top_level_dirs_to_exclude: set[str] | None = None,
31
+ target: Path,
32
+ root_directory: Path,
33
+ exclude_venv_directories: bool,
34
+ include_db_file: bool = False,
35
+ directory_names_to_exclude: set[str] | None = None,
36
+ files_to_exclude: set[Path] | None = None,
37
37
  globs_to_include: list[str] | None = None,
38
38
  ) -> None:
39
39
  """Zip utility function.
@@ -41,49 +41,62 @@ def _zip(
41
41
  Args:
42
42
  component_name: The name of the component: backend or frontend.
43
43
  target: The target zip file.
44
- root_dir: The root directory to zip.
45
- exclude_venv_dirs: Whether to exclude venv directories.
46
- upload_db_file: Whether to include local sqlite db files.
47
- dirs_to_exclude: The directories to exclude.
44
+ root_directory: The root directory to zip.
45
+ exclude_venv_directories: Whether to exclude venv directories.
46
+ include_db_file: Whether to include local sqlite db files.
47
+ directory_names_to_exclude: The directory names to exclude.
48
48
  files_to_exclude: The files to exclude.
49
- top_level_dirs_to_exclude: The top level directory names immediately under root_dir to exclude. Do not exclude folders by these names further in the sub-directories.
50
- globs_to_include: Apply these globs from the root_dir and always include them in the zip.
49
+ globs_to_include: Apply these globs from the root_directory and always include them in the zip.
51
50
 
52
51
  """
53
52
  target = Path(target)
54
- root_dir = Path(root_dir)
55
- dirs_to_exclude = dirs_to_exclude or set()
53
+ root_directory = Path(root_directory).resolve()
54
+ directory_names_to_exclude = directory_names_to_exclude or set()
56
55
  files_to_exclude = files_to_exclude or set()
57
- files_to_zip: list[str] = []
56
+ files_to_zip: list[Path] = []
58
57
  # Traverse the root directory in a top-down manner. In this traversal order,
59
58
  # we can modify the dirs list in-place to remove directories we don't want to include.
60
- for root, dirs, files in os.walk(root_dir, topdown=True, followlinks=True):
61
- root = Path(root)
59
+ for directory_path, subdirectories_names, subfiles_names in os.walk(
60
+ root_directory, topdown=True, followlinks=True
61
+ ):
62
+ directory_path = Path(directory_path).resolve()
62
63
  # Modify the dirs in-place so excluded and hidden directories are skipped in next traversal.
63
- dirs[:] = [
64
- d
65
- for d in dirs
66
- if (basename := Path(d).resolve().name) not in dirs_to_exclude
67
- and not basename.startswith(".")
68
- and (not exclude_venv_dirs or not _looks_like_venv_dir(root / d))
64
+ subdirectories_names[:] = [
65
+ subdirectory_name
66
+ for subdirectory_name in subdirectories_names
67
+ if subdirectory_name not in directory_names_to_exclude
68
+ and not any(
69
+ (directory_path / subdirectory_name).samefile(exclude)
70
+ for exclude in files_to_exclude
71
+ if exclude.exists()
72
+ )
73
+ and not subdirectory_name.startswith(".")
74
+ and (
75
+ not exclude_venv_directories
76
+ or not _looks_like_venv_directory(directory_path / subdirectory_name)
77
+ )
69
78
  ]
70
- # If we are at the top level with root_dir, exclude the top level dirs.
71
- if top_level_dirs_to_exclude and root == root_dir:
72
- dirs[:] = [d for d in dirs if d not in top_level_dirs_to_exclude]
73
79
  # Modify the files in-place so the hidden files and db files are excluded.
74
- files[:] = [
75
- f
76
- for f in files
77
- if not f.startswith(".") and (upload_db_file or not f.endswith(".db"))
80
+ subfiles_names[:] = [
81
+ subfile_name
82
+ for subfile_name in subfiles_names
83
+ if not subfile_name.startswith(".")
84
+ and (include_db_file or not subfile_name.endswith(".db"))
78
85
  ]
79
86
  files_to_zip += [
80
- str(root / file) for file in files if file not in files_to_exclude
87
+ directory_path / subfile_name
88
+ for subfile_name in subfiles_names
89
+ if not any(
90
+ (directory_path / subfile_name).samefile(excluded_file)
91
+ for excluded_file in files_to_exclude
92
+ if excluded_file.exists()
93
+ )
81
94
  ]
82
95
  if globs_to_include:
83
96
  for glob in globs_to_include:
84
97
  files_to_zip += [
85
- str(file)
86
- for file in root_dir.glob(glob)
98
+ file
99
+ for file in root_directory.glob(glob)
87
100
  if file.name not in files_to_exclude
88
101
  ]
89
102
  # Create a progress bar for zipping the component.
@@ -100,14 +113,15 @@ def _zip(
100
113
  for file in files_to_zip:
101
114
  console.debug(f"{target}: {file}", progress=progress)
102
115
  progress.advance(task)
103
- zipf.write(file, Path(file).relative_to(root_dir))
116
+ zipf.write(file, Path(file).relative_to(root_directory))
104
117
 
105
118
 
106
119
  def zip_app(
107
120
  frontend: bool = True,
108
121
  backend: bool = True,
109
122
  zip_dest_dir: str | Path | None = None,
110
- upload_db_file: bool = False,
123
+ include_db_file: bool = False,
124
+ backend_excluded_dirs: tuple[Path, ...] = (),
111
125
  ):
112
126
  """Zip up the app.
113
127
 
@@ -115,41 +129,41 @@ def zip_app(
115
129
  frontend: Whether to zip up the frontend app.
116
130
  backend: Whether to zip up the backend app.
117
131
  zip_dest_dir: The directory to export the zip file to.
118
- upload_db_file: Whether to upload the database file.
132
+ include_db_file: Whether to include the database file.
133
+ backend_excluded_dirs: A tuple of files or directories to exclude from the backend zip. Defaults to ().
119
134
  """
120
135
  zip_dest_dir = zip_dest_dir or Path.cwd()
121
136
  zip_dest_dir = Path(zip_dest_dir)
122
137
  files_to_exclude = {
123
- constants.ComponentName.FRONTEND.zip(),
124
- constants.ComponentName.BACKEND.zip(),
138
+ Path(constants.ComponentName.FRONTEND.zip()).resolve(),
139
+ Path(constants.ComponentName.BACKEND.zip()).resolve(),
125
140
  }
126
141
 
127
142
  if frontend:
128
143
  _zip(
129
144
  component_name=constants.ComponentName.FRONTEND,
130
145
  target=zip_dest_dir / constants.ComponentName.FRONTEND.zip(),
131
- root_dir=prerequisites.get_web_dir() / constants.Dirs.STATIC,
146
+ root_directory=prerequisites.get_web_dir() / constants.Dirs.STATIC,
132
147
  files_to_exclude=files_to_exclude,
133
- exclude_venv_dirs=False,
148
+ exclude_venv_directories=False,
134
149
  )
135
150
 
136
151
  if backend:
137
152
  _zip(
138
153
  component_name=constants.ComponentName.BACKEND,
139
154
  target=zip_dest_dir / constants.ComponentName.BACKEND.zip(),
140
- root_dir=Path.cwd(),
141
- dirs_to_exclude={"__pycache__"},
142
- files_to_exclude=files_to_exclude,
143
- top_level_dirs_to_exclude={"assets"},
144
- exclude_venv_dirs=True,
145
- upload_db_file=upload_db_file,
155
+ root_directory=Path.cwd(),
156
+ directory_names_to_exclude={"__pycache__"},
157
+ files_to_exclude=files_to_exclude | set(backend_excluded_dirs),
158
+ exclude_venv_directories=True,
159
+ include_db_file=include_db_file,
146
160
  globs_to_include=[
147
161
  str(Path(constants.Dirs.WEB) / constants.Dirs.BACKEND / "*")
148
162
  ],
149
163
  )
150
164
 
151
165
 
152
- def _duplicate_index_html_to_parent_dir(directory: Path):
166
+ def _duplicate_index_html_to_parent_directory(directory: Path):
153
167
  """Duplicate index.html in the child directories to the given directory.
154
168
 
155
169
  This makes accessing /route and /route/ work in production.
@@ -169,7 +183,7 @@ def _duplicate_index_html_to_parent_dir(directory: Path):
169
183
  else:
170
184
  console.debug(f"Skipping {index_html}, already exists at {target}")
171
185
  # Recursively call this function for the child directory.
172
- _duplicate_index_html_to_parent_dir(child)
186
+ _duplicate_index_html_to_parent_directory(child)
173
187
 
174
188
 
175
189
  def build():
@@ -200,7 +214,7 @@ def build():
200
214
  },
201
215
  )
202
216
  processes.show_progress("Creating Production Build", process, checkpoints)
203
- _duplicate_index_html_to_parent_dir(wdir / constants.Dirs.STATIC)
217
+ _duplicate_index_html_to_parent_directory(wdir / constants.Dirs.STATIC)
204
218
  path_ops.cp(
205
219
  wdir / constants.Dirs.STATIC / constants.ReactRouter.SPA_FALLBACK,
206
220
  wdir / constants.Dirs.STATIC / "404.html",
@@ -247,6 +261,6 @@ def setup_frontend_prod(
247
261
  build()
248
262
 
249
263
 
250
- def _looks_like_venv_dir(dir_to_check: str | Path) -> bool:
251
- dir_to_check = Path(dir_to_check)
252
- return (dir_to_check / "pyvenv.cfg").exists()
264
+ def _looks_like_venv_directory(directory_to_check: str | Path) -> bool:
265
+ directory_to_check = Path(directory_to_check)
266
+ return (directory_to_check / "pyvenv.cfg").exists()
reflex/utils/exec.py CHANGED
@@ -497,6 +497,7 @@ HOTRELOAD_IGNORE_EXTENSIONS = (
497
497
  "sh",
498
498
  "bash",
499
499
  "log",
500
+ "db",
500
501
  )
501
502
 
502
503
  HOTRELOAD_IGNORE_PATTERNS = (
reflex/utils/export.py CHANGED
@@ -18,6 +18,7 @@ def export(
18
18
  deploy_url: str | None = None,
19
19
  env: constants.Env = constants.Env.PROD,
20
20
  loglevel: constants.LogLevel = console._LOG_LEVEL,
21
+ backend_excluded_dirs: tuple[Path, ...] = (),
21
22
  ):
22
23
  """Export the app to a zip file.
23
24
 
@@ -31,6 +32,7 @@ def export(
31
32
  deploy_url: The deploy URL to use. Defaults to None.
32
33
  env: The environment to use. Defaults to constants.Env.PROD.
33
34
  loglevel: The log level to use. Defaults to console._LOG_LEVEL.
35
+ backend_excluded_dirs: A tuple of files or directories to exclude from the backend zip. Defaults to ().
34
36
  """
35
37
  config = get_config()
36
38
 
@@ -70,7 +72,8 @@ def export(
70
72
  frontend=frontend,
71
73
  backend=backend,
72
74
  zip_dest_dir=zip_dest_dir,
73
- upload_db_file=upload_db_file,
75
+ include_db_file=upload_db_file,
76
+ backend_excluded_dirs=backend_excluded_dirs,
74
77
  )
75
78
 
76
79
  # Post a telemetry event.
@@ -66,6 +66,16 @@ class TokenManager(ABC):
66
66
 
67
67
  return LocalTokenManager()
68
68
 
69
+ async def disconnect_all(self):
70
+ """Disconnect all tracked tokens when the server is going down."""
71
+ token_sid_pairs: set[tuple[str, str]] = set(self.token_to_sid.items())
72
+ token_sid_pairs.update(
73
+ ((token, sid) for sid, token in self.sid_to_token.items())
74
+ )
75
+ # Perform the disconnection logic here
76
+ for token, sid in token_sid_pairs:
77
+ await self.disconnect_token(token, sid)
78
+
69
79
 
70
80
  class LocalTokenManager(TokenManager):
71
81
  """Token manager using local in-memory dictionaries (single worker)."""
reflex/utils/types.py CHANGED
@@ -6,6 +6,7 @@ import dataclasses
6
6
  import sys
7
7
  import types
8
8
  from collections.abc import Callable, Iterable, Mapping, Sequence
9
+ from enum import Enum
9
10
  from functools import cached_property, lru_cache
10
11
  from types import GenericAlias
11
12
  from typing import ( # noqa: UP035
@@ -1241,6 +1242,7 @@ IMMUTABLE_TYPES = (
1241
1242
  frozenset,
1242
1243
  tuple,
1243
1244
  type(None),
1245
+ Enum,
1244
1246
  )
1245
1247
 
1246
1248
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reflex
3
- Version: 0.8.12a1
3
+ Version: 0.8.12a2
4
4
  Summary: Web apps in pure Python.
5
5
  Project-URL: homepage, https://reflex.dev
6
6
  Project-URL: repository, https://github.com/reflex-dev/reflex
@@ -2,7 +2,7 @@ reflex/__init__.py,sha256=_1PVYjDeA6_JyfXvL6OuKjjO6AX2oMiNcAq8AEHf6xw,10161
2
2
  reflex/__init__.pyi,sha256=0D46kHVUJPE_kgYL-BjraERu-MXNCPsQTZQShrijmeQ,10148
3
3
  reflex/__main__.py,sha256=6cVrGEyT3j3tEvlEVUatpaYfbB5EF3UVY-6vc_Z7-hw,108
4
4
  reflex/admin.py,sha256=Nbc38y-M8iaRBvh1W6DQu_D3kEhO8JFvxrog4q2cB_E,434
5
- reflex/app.py,sha256=K3HgJEVKOVEINNOGc8mIT7NIwLolnzs6aUBObm5zJ64,78485
5
+ reflex/app.py,sha256=LCIj6hU3AbJJpVipG3AIBJfNK1eKGjn_SRTtg5MH2vw,78526
6
6
  reflex/assets.py,sha256=l5O_mlrTprC0lF7Rc_McOe3a0OtSLnRdNl_PqCpDCBA,3431
7
7
  reflex/base.py,sha256=Oh664QL3fZEHErhUasFqP7fE4olYf1y-9Oj6uZI2FCU,1173
8
8
  reflex/config.py,sha256=LsHAtdH4nkSn3q_Ie-KNdOGdflLXrFICUQov29oFjVk,21229
@@ -11,11 +11,11 @@ reflex/event.py,sha256=e2EIBmLF63KRw4GXSbuiQ6yDp1-YQHHIxJKenTra9vk,76223
11
11
  reflex/model.py,sha256=2QhU1TJlcDeRA23pv8usLjyDaA6FhbQRYdzsjOHzvUI,19665
12
12
  reflex/page.py,sha256=ssCbMVFuIy60vH-YhJUzN0OxzUwXFCCD3ej56dVjp3g,3525
13
13
  reflex/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- reflex/reflex.py,sha256=qKpWvXuA55y519MWckr6oxKSf3pCQfRCrvzsyXSuAqk,22729
14
+ reflex/reflex.py,sha256=5R-NYJCrlmZoOdIGnO1NyBdNLQjfpqiJNaPuLXaxvzY,23459
15
15
  reflex/route.py,sha256=TnS4m6Hm-b3LfGFpm37iAMEd-_JISAouPW5FqUxTAfU,7858
16
- reflex/state.py,sha256=gqQNmLGdrG9sxivfbyE1bHrLSlYZarbQCt6PtPaWO2w,94991
16
+ reflex/state.py,sha256=wQ4ozzgeTQHVeX4-zHhQfyHHDaykHdka6B5xGvo-5UQ,95000
17
17
  reflex/style.py,sha256=Jc7hZyH9CSFDbweoRCrkVtSu8tZq5aIggSoAYAh-w1M,13304
18
- reflex/testing.py,sha256=wLhvUdvTa6jPDkfdWLOYt69piX1KFhYxZfEjQzWfJo0,39902
18
+ reflex/testing.py,sha256=Wt1qdmqT3Yo9CKWZzHXiytizZJ5KjqxVzuoGPjI_Vfk,40458
19
19
  reflex/.templates/apps/blank/assets/favicon.ico,sha256=baxxgDAQ2V4-G5Q4S2yK5uUJTUGkv-AOWBQ0xd6myUo,4286
20
20
  reflex/.templates/apps/blank/code/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  reflex/.templates/apps/blank/code/blank.py,sha256=wry9E3VjC7qtt_gzqNOyo4KZAAlzVyNp3uhFkcLZmM0,898
@@ -30,7 +30,7 @@ reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js,sha2
30
30
  reflex/.templates/web/components/shiki/code.js,sha256=4Es1pxsr-lX4hTQ5mglrwwC6O_SI-z-O60k03z8VFzQ,1144
31
31
  reflex/.templates/web/styles/__reflex_style_reset.css,sha256=qbC6JIT643YEsvSQ0D7xBmWE5vXy94JGrKNihRuEjnA,8913
32
32
  reflex/.templates/web/utils/react-theme.js,sha256=Aa-RND3ooGCXW6Zavzitc-v0ciKlcQDTFlDtE4mPkFI,2713
33
- reflex/.templates/web/utils/state.js,sha256=Hqx-h7bGSSaaHA0z_6sEWWqv6V08LFa8nh_nPKOhEf0,32155
33
+ reflex/.templates/web/utils/state.js,sha256=RdNHwQzS_iP0Z_fx-H4pjVjl-qUoz31JLvwPwcp0F-s,33046
34
34
  reflex/.templates/web/utils/helpers/dataeditor.js,sha256=pG6MgsHuStDR7-qPipzfiK32j9bKDBa-4hZ0JSUo4JM,1623
35
35
  reflex/.templates/web/utils/helpers/debounce.js,sha256=xGhtTRtS_xIcaeqnYVvYJNseLgQVk-DW-eFiHJYO9As,528
36
36
  reflex/.templates/web/utils/helpers/paste.js,sha256=ef30HsR83jRzzvZnl8yV79yqFP8TC_u8SlN99cCS_OM,1799
@@ -38,11 +38,11 @@ reflex/.templates/web/utils/helpers/range.js,sha256=Bjr7Ex1Mghpsopjfrcp__IVFw8F8
38
38
  reflex/.templates/web/utils/helpers/throttle.js,sha256=qxeyaEojaTeX36FPGftzVWrzDsRQU4iqg3U9RJz9Vj4,566
39
39
  reflex/.templates/web/utils/helpers/upload.js,sha256=YiAv-KC-LijTsEmzG_o6YxUprgSN7Pbyd__xu8HTX6s,4814
40
40
  reflex/app_mixins/__init__.py,sha256=Oegz3-gZLP9p2OAN5ALNbsgxuNQfS6lGZgQA8cc-9mQ,137
41
- reflex/app_mixins/lifespan.py,sha256=a156ZUYVo2bN1Tv-4WmWSjojo90PP_2-V12BX0q8YNw,3543
41
+ reflex/app_mixins/lifespan.py,sha256=_JMloSOpmpPpAPpVMYO42LW3-6DxPbx0fcRsC2VVyM8,4028
42
42
  reflex/app_mixins/middleware.py,sha256=BKhe0jUFO1_TylEC48LUZyaeYyPmAYW-NV4H5Rw221k,2848
43
43
  reflex/app_mixins/mixin.py,sha256=R1YncalqDrbdPZvpKVbm72ZKmQZxYAWfuFq9JknzTqQ,305
44
44
  reflex/compiler/__init__.py,sha256=r8jqmDSFf09iV2lHlNhfc9XrTLjNxfDNwPYlxS4cmHE,27
45
- reflex/compiler/compiler.py,sha256=rE0aMl_xwq-qvPd6fgvUV_F4qaMiNKo_82LfCKbX258,29114
45
+ reflex/compiler/compiler.py,sha256=-MnavauFrb9xmePf0VsFB0vLlE6Nr5zY8x7kne2Uzvg,29156
46
46
  reflex/compiler/templates.py,sha256=ucfkEMOGA49ceUnL1KXDP_oHKxPrELSKWtRgtXUVHjk,20547
47
47
  reflex/compiler/utils.py,sha256=RmeUoZMHdIfnqPl-p0ToPgwd0FEFO5u0Xbb-J20UYdQ,19621
48
48
  reflex/components/__init__.py,sha256=eWpgWFbSQDj2TpGp6StEbxU7roQgzY7ZM0XIcIc5RE8,588
@@ -95,7 +95,7 @@ reflex/components/core/match.py,sha256=xBB9vtWgVlotPHq6ssng8lzxwXDDQLp9k6Ew5RPPd
95
95
  reflex/components/core/responsive.py,sha256=ACZdtJ4a4F8B3dm1k8h6J2_UJx0Z5LDB7XHQ2ty4wAc,1911
96
96
  reflex/components/core/sticky.py,sha256=2B3TxrwG2Rtp_lv1VkMOIF2bqSiT7qYGbqbiZiMKxKY,3856
97
97
  reflex/components/core/sticky.pyi,sha256=5D-yT0LYs0ewOlUlInU7KCpuz49yKK7dirysUs1C2VI,32908
98
- reflex/components/core/upload.py,sha256=OEjXq4O06YSoRlJvYs-hyFJgo7GzQJByO9RqPI1RUzc,13585
98
+ reflex/components/core/upload.py,sha256=V0vIYig6XoOstQY2KDH88OYmLYPuSzkQopEYm3GZ81E,15195
99
99
  reflex/components/core/upload.pyi,sha256=UqfcPGUs8xmnKHKuvqYV7CtOXeF_D1s9ooRe49w6C3E,15757
100
100
  reflex/components/core/window_events.py,sha256=opbuO20zVxt252kQLk49V7cltb_Um2oh7iePeGNJ538,3355
101
101
  reflex/components/core/window_events.pyi,sha256=aTkBiAy-e9LqkQm6_apRsXXfJRdawA11cE1tQQSIy3c,3206
@@ -330,7 +330,7 @@ reflex/istate/__init__.py,sha256=afq_pCS5B_REC-Kl3Rbaa538uWi59xNz4INeuENcWnk,203
330
330
  reflex/istate/data.py,sha256=8RydiarP7f5ET5a3dfGpuuXdYZ7KHEWS6aENVoxRxGc,7918
331
331
  reflex/istate/dynamic.py,sha256=xOQ9upZVPf6ngqcLQZ9HdAAYmoWwJ8kRFPH34Q5HTiM,91
332
332
  reflex/istate/manager.py,sha256=WyBBhDuq3cVuno3H-axeT8sqF0EqHR3MNZJw-B76cZs,30517
333
- reflex/istate/proxy.py,sha256=Q8JrV1m6drVcTNJL9JuN-nKUXclazs96OHl_fhR0UBk,25928
333
+ reflex/istate/proxy.py,sha256=4SE3-W6OpHA51JlLdz5Kbslpusp5xOUQI91nmzpH8Xo,25825
334
334
  reflex/istate/storage.py,sha256=gCPoiZxuG-Rw0y-Pz3OC7rv4o08dQ_jK1fE2u8Jhxqg,4339
335
335
  reflex/istate/wrappers.py,sha256=p8uuioXRbR5hperwbOJHUcWdu7hukLikQdoR7qrnKsI,909
336
336
  reflex/middleware/__init__.py,sha256=x7xTeDuc73Hjj43k1J63naC9x8vzFxl4sq7cCFBX7sk,111
@@ -344,14 +344,14 @@ reflex/plugins/sitemap.py,sha256=X_CtH5B1w3CZno-gdPj1rp63WjOuNjFnX4B3fx_-VFQ,613
344
344
  reflex/plugins/tailwind_v3.py,sha256=jCEZ5UYdr706Mw48L-WSHOUB6O55o1C3uG6AMwXqZoI,4810
345
345
  reflex/plugins/tailwind_v4.py,sha256=fcNaFtikSIu1LhF94DcBs1xR2CjbQRB5o1_KYeThUF0,5230
346
346
  reflex/utils/__init__.py,sha256=y-AHKiRQAhk2oAkvn7W8cRVTZVK625ff8tTwvZtO7S4,24
347
- reflex/utils/build.py,sha256=GLT2ycqgAe1cw__MFbfdlYrkzcTnY1oJ8cAv80jEcnQ,8641
347
+ reflex/utils/build.py,sha256=wwpG1K5WZGbMSsX9iF8hA5kJCbawtJizRlkotgovzQs,9287
348
348
  reflex/utils/codespaces.py,sha256=kEQ-j-jclTukFpXDlYgNp95kYMGDrQmP3VNEoYGZ1u4,3052
349
349
  reflex/utils/compat.py,sha256=aSJH_M6iomgHPQ4onQ153xh1MWqPi3HSYDzE68N6gZM,2635
350
350
  reflex/utils/console.py,sha256=W41Ogj1Jk8tEOhXXy9dy4KCLYp5rn0NZQwbBqXbkwSI,13668
351
351
  reflex/utils/decorator.py,sha256=QUZntENupeW5FA5mNRTx0I1GzGKFQXhMjVg24_IIM5o,3957
352
352
  reflex/utils/exceptions.py,sha256=Wwu7Ji2xgq521bJKtU2NgjwhmFfnG8erirEVN2h8S-g,8884
353
- reflex/utils/exec.py,sha256=x0I45o0JTB2vj2CYi5SdnTiRXIe8uu2pXCRFqESz5Uw,21960
354
- reflex/utils/export.py,sha256=Z2AHuhkxGQzOi9I90BejQ4qEcD0URr2i-ZU5qTJt7eQ,2562
353
+ reflex/utils/exec.py,sha256=RsUpJsXLoQKMF4tMWPKP2xjvKcDP1OyNXU0gC__7RSU,21970
354
+ reflex/utils/export.py,sha256=jQvOP09LVlCpznsOWqZ9AQzBHwyhZPcDqZ5iuAvrVNM,2783
355
355
  reflex/utils/format.py,sha256=-EC0tfx7VCIijcuJx9l-ArRnRnPKrrrW8RgsKwXIoBc,21115
356
356
  reflex/utils/frontend_skeleton.py,sha256=8sXRmVsKUiWf6O5rA1RRR_E4YjEsqDdIQlkUByp3aOI,8774
357
357
  reflex/utils/imports.py,sha256=SlQfMTbJasXHxrpcHdWKPWiIZ1Kn2-tulMF32_YA2ek,4262
@@ -370,8 +370,8 @@ reflex/utils/rename.py,sha256=qdE4SXHOaNs-TDGrnJz-h_nvLWA1C5osVrWb4wLSfyI,5262
370
370
  reflex/utils/serializers.py,sha256=ZqQ2xrBIyOXaN0RIRZwy2oU5O0Y1R0SEGWx-kf5rXMs,13938
371
371
  reflex/utils/telemetry.py,sha256=KY54NmGWyJVSf9TMTcXw2V6gIbEqut1JkAXmmtIlRfw,10776
372
372
  reflex/utils/templates.py,sha256=tWo3jO6laQX8b0gUsqHkio_hUQGIvFbmXC-lxiGcdRo,14251
373
- reflex/utils/token_manager.py,sha256=o_HGbqT9WfYRmek2iY9nem4vDZMz8Q4Dra-eW1lKmuA,6999
374
- reflex/utils/types.py,sha256=Xh9jXSMBgwrR-Whn_5qAnjqQWzHiIJbm1b8qwMG4QmY,38511
373
+ reflex/utils/token_manager.py,sha256=ZtrYR0X8tTs8FpQHtMb09-H2V1xSoLWwVH8jW8OCrU8,7445
374
+ reflex/utils/types.py,sha256=v2shXUDPqsgrxXDwrP9JYYgSTwZht0YjAo5c1mDDI8M,38543
375
375
  reflex/vars/__init__.py,sha256=85eXMt32bFoKtMdH3KxYRMD8mtnKyYiQcThPxJLoW1k,1359
376
376
  reflex/vars/base.py,sha256=LKnjDvg7_XKzPZlDD9WfYbWzyMoSjiZ7Bdt4sICjW60,113344
377
377
  reflex/vars/datetime.py,sha256=F2Jv_bfydipFSkIQ1F6x5MnSgFEyES9Vq5RG_uGH81E,5118
@@ -381,8 +381,8 @@ reflex/vars/number.py,sha256=tO7pnvFaBsedq1HWT4skytnSqHWMluGEhUbjAUMx8XQ,28190
381
381
  reflex/vars/object.py,sha256=p7dyn9rD6rVJlHQ_RcDTBgsU_AlbwYklGjx1HK3XxZg,16565
382
382
  reflex/vars/sequence.py,sha256=1kBrqihspyjyQ1XDqFPC8OpVGtZs_EVkOdIKBro5ilA,55249
383
383
  scripts/hatch_build.py,sha256=-4pxcLSFmirmujGpQX9UUxjhIC03tQ_fIQwVbHu9kc0,1861
384
- reflex-0.8.12a1.dist-info/METADATA,sha256=1mlLvn8KLKt-7gFEixs_LGbugmbrrPoBDb_pLdG684w,12336
385
- reflex-0.8.12a1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
386
- reflex-0.8.12a1.dist-info/entry_points.txt,sha256=Rxt4dXc7MLBNt5CSHTehVPuSe9Xqow4HLX55nD9tQQ0,45
387
- reflex-0.8.12a1.dist-info/licenses/LICENSE,sha256=dw3zLrp9f5ObD7kqS32vWfhcImfO52PMmRqvtxq_YEE,11358
388
- reflex-0.8.12a1.dist-info/RECORD,,
384
+ reflex-0.8.12a2.dist-info/METADATA,sha256=6T1DqZw5zqu08qGQf6voKyO5NSIrhbMkzsvHPa7SaT8,12336
385
+ reflex-0.8.12a2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
386
+ reflex-0.8.12a2.dist-info/entry_points.txt,sha256=Rxt4dXc7MLBNt5CSHTehVPuSe9Xqow4HLX55nD9tQQ0,45
387
+ reflex-0.8.12a2.dist-info/licenses/LICENSE,sha256=dw3zLrp9f5ObD7kqS32vWfhcImfO52PMmRqvtxq_YEE,11358
388
+ reflex-0.8.12a2.dist-info/RECORD,,