reflex 0.8.15a1__py3-none-any.whl → 0.8.16a1__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 (139) hide show
  1. reflex/.templates/web/utils/state.js +68 -8
  2. reflex/app.py +45 -51
  3. reflex/app_mixins/lifespan.py +12 -5
  4. reflex/base.py +1 -0
  5. reflex/compiler/compiler.py +4 -6
  6. reflex/compiler/templates.py +25 -31
  7. reflex/compiler/utils.py +6 -5
  8. reflex/components/base/body.pyi +1 -195
  9. reflex/components/base/link.pyi +1 -407
  10. reflex/components/base/meta.pyi +1 -405
  11. reflex/components/base/script.pyi +1 -237
  12. reflex/components/component.py +41 -46
  13. reflex/components/core/auto_scroll.pyi +1 -195
  14. reflex/components/core/banner.pyi +1 -391
  15. reflex/components/core/breakpoints.py +14 -18
  16. reflex/components/core/html.pyi +1 -197
  17. reflex/components/core/match.py +2 -2
  18. reflex/components/core/sticky.py +11 -15
  19. reflex/components/core/sticky.pyi +0 -788
  20. reflex/components/core/upload.py +1 -3
  21. reflex/components/datadisplay/code.pyi +1 -0
  22. reflex/components/datadisplay/dataeditor.py +4 -6
  23. reflex/components/datadisplay/shiki_code_block.py +106 -110
  24. reflex/components/dynamic.py +4 -6
  25. reflex/components/el/elements/__init__.py +5 -7
  26. reflex/components/el/elements/__init__.pyi +5 -7
  27. reflex/components/el/elements/base.py +1 -1
  28. reflex/components/el/elements/base.pyi +1 -195
  29. reflex/components/el/elements/forms.py +7 -9
  30. reflex/components/el/elements/forms.pyi +12 -3112
  31. reflex/components/el/elements/inline.pyi +0 -5481
  32. reflex/components/el/elements/media.pyi +0 -10280
  33. reflex/components/el/elements/metadata.pyi +1 -835
  34. reflex/components/el/elements/other.pyi +1 -1365
  35. reflex/components/el/elements/scripts.pyi +1 -625
  36. reflex/components/el/elements/sectioning.pyi +1 -2911
  37. reflex/components/el/elements/tables.pyi +1 -1973
  38. reflex/components/el/elements/typography.pyi +1 -3125
  39. reflex/components/lucide/icon.py +4 -4
  40. reflex/components/lucide/icon.pyi +0 -4
  41. reflex/components/markdown/markdown.py +15 -19
  42. reflex/components/markdown/markdown.pyi +1 -0
  43. reflex/components/moment/moment.pyi +0 -49
  44. reflex/components/props.py +3 -3
  45. reflex/components/radix/primitives/accordion.py +4 -6
  46. reflex/components/radix/primitives/accordion.pyi +0 -14
  47. reflex/components/radix/primitives/base.pyi +0 -5
  48. reflex/components/radix/primitives/dialog.py +2 -0
  49. reflex/components/radix/primitives/dialog.pyi +1 -233
  50. reflex/components/radix/primitives/drawer.pyi +0 -18
  51. reflex/components/radix/primitives/form.pyi +30 -632
  52. reflex/components/radix/primitives/progress.pyi +0 -10
  53. reflex/components/radix/primitives/slider.pyi +0 -10
  54. reflex/components/radix/themes/color_mode.pyi +1 -284
  55. reflex/components/radix/themes/components/alert_dialog.pyi +0 -207
  56. reflex/components/radix/themes/components/aspect_ratio.pyi +0 -2
  57. reflex/components/radix/themes/components/avatar.pyi +0 -80
  58. reflex/components/radix/themes/components/badge.pyi +1 -270
  59. reflex/components/radix/themes/components/button.pyi +1 -274
  60. reflex/components/radix/themes/components/callout.pyi +0 -1197
  61. reflex/components/radix/themes/components/card.pyi +1 -209
  62. reflex/components/radix/themes/components/checkbox.pyi +0 -261
  63. reflex/components/radix/themes/components/checkbox_cards.pyi +1 -96
  64. reflex/components/radix/themes/components/checkbox_group.pyi +1 -80
  65. reflex/components/radix/themes/components/context_menu.pyi +13 -321
  66. reflex/components/radix/themes/components/data_list.pyi +1 -107
  67. reflex/components/radix/themes/components/dialog.pyi +1 -210
  68. reflex/components/radix/themes/components/dropdown_menu.pyi +0 -209
  69. reflex/components/radix/themes/components/hover_card.pyi +1 -246
  70. reflex/components/radix/themes/components/icon_button.pyi +1 -195
  71. reflex/components/radix/themes/components/inset.pyi +0 -252
  72. reflex/components/radix/themes/components/popover.pyi +1 -234
  73. reflex/components/radix/themes/components/progress.pyi +1 -84
  74. reflex/components/radix/themes/components/radio.pyi +1 -72
  75. reflex/components/radix/themes/components/radio_cards.pyi +1 -123
  76. reflex/components/radix/themes/components/scroll_area.pyi +1 -11
  77. reflex/components/radix/themes/components/select.pyi +1 -376
  78. reflex/components/radix/themes/components/separator.pyi +0 -77
  79. reflex/components/radix/themes/components/skeleton.pyi +0 -30
  80. reflex/components/radix/themes/components/slider.py +3 -5
  81. reflex/components/radix/themes/components/spinner.pyi +0 -5
  82. reflex/components/radix/themes/components/switch.pyi +0 -89
  83. reflex/components/radix/themes/components/table.pyi +0 -1453
  84. reflex/components/radix/themes/components/text_area.pyi +7 -282
  85. reflex/components/radix/themes/components/text_field.pyi +6 -392
  86. reflex/components/radix/themes/components/tooltip.pyi +0 -42
  87. reflex/components/radix/themes/layout/box.pyi +1 -195
  88. reflex/components/radix/themes/layout/center.pyi +0 -194
  89. reflex/components/radix/themes/layout/container.pyi +0 -178
  90. reflex/components/radix/themes/layout/flex.pyi +0 -194
  91. reflex/components/radix/themes/layout/grid.pyi +0 -194
  92. reflex/components/radix/themes/layout/list.pyi +0 -978
  93. reflex/components/radix/themes/layout/section.pyi +0 -194
  94. reflex/components/radix/themes/layout/spacer.pyi +0 -194
  95. reflex/components/radix/themes/layout/stack.pyi +0 -582
  96. reflex/components/radix/themes/typography/blockquote.pyi +0 -196
  97. reflex/components/radix/themes/typography/code.pyi +0 -194
  98. reflex/components/radix/themes/typography/heading.pyi +0 -194
  99. reflex/components/radix/themes/typography/link.pyi +0 -237
  100. reflex/components/radix/themes/typography/text.pyi +0 -1360
  101. reflex/components/react_router/dom.pyi +0 -237
  102. reflex/components/recharts/cartesian.py +12 -18
  103. reflex/components/recharts/general.py +12 -18
  104. reflex/constants/installer.py +5 -5
  105. reflex/custom_components/custom_components.py +6 -5
  106. reflex/environment.py +30 -7
  107. reflex/event.py +14 -12
  108. reflex/experimental/client_state.py +11 -12
  109. reflex/istate/data.py +8 -10
  110. reflex/istate/manager/__init__.py +3 -0
  111. reflex/istate/manager/disk.py +151 -5
  112. reflex/model.py +1 -1
  113. reflex/plugins/_screenshot.py +2 -2
  114. reflex/plugins/shared_tailwind.py +9 -14
  115. reflex/reflex.py +7 -9
  116. reflex/state.py +30 -37
  117. reflex/style.py +6 -6
  118. reflex/testing.py +54 -30
  119. reflex/utils/codespaces.py +31 -2
  120. reflex/utils/compat.py +1 -0
  121. reflex/utils/decorator.py +3 -3
  122. reflex/utils/format.py +18 -22
  123. reflex/utils/prerequisites.py +1 -1
  124. reflex/utils/pyi_generator.py +51 -57
  125. reflex/utils/serializers.py +1 -1
  126. reflex/utils/telemetry.py +1 -1
  127. reflex/utils/templates.py +4 -4
  128. reflex/utils/types.py +11 -4
  129. reflex/vars/base.py +26 -29
  130. reflex/vars/color.py +6 -8
  131. reflex/vars/dep_tracking.py +5 -3
  132. reflex/vars/function.py +3 -3
  133. reflex/vars/object.py +9 -13
  134. reflex/vars/sequence.py +18 -24
  135. {reflex-0.8.15a1.dist-info → reflex-0.8.16a1.dist-info}/METADATA +1 -1
  136. {reflex-0.8.15a1.dist-info → reflex-0.8.16a1.dist-info}/RECORD +139 -139
  137. {reflex-0.8.15a1.dist-info → reflex-0.8.16a1.dist-info}/WHEEL +0 -0
  138. {reflex-0.8.15a1.dist-info → reflex-0.8.16a1.dist-info}/entry_points.txt +0 -0
  139. {reflex-0.8.15a1.dist-info → reflex-0.8.16a1.dist-info}/licenses/LICENSE +0 -0
@@ -529,6 +529,14 @@ export const connect = async (
529
529
  navigate,
530
530
  params,
531
531
  ) => {
532
+ // Socket already allocated, just reconnect it if needed.
533
+ if (socket.current) {
534
+ if (!socket.current.connected) {
535
+ socket.current.reconnect();
536
+ }
537
+ return;
538
+ }
539
+
532
540
  // Get backend URL object from the endpoint.
533
541
  const endpoint = getBackendURL(EVENTURL);
534
542
  const on_hydrated_queue = [];
@@ -540,7 +548,9 @@ export const connect = async (
540
548
  protocols: [reflexEnvironment.version],
541
549
  autoUnref: false,
542
550
  query: { token: getToken() },
551
+ reconnection: false, // Reconnection will be handled manually.
543
552
  });
553
+ socket.current.wait_connect = !socket.current.connected;
544
554
  // Ensure undefined fields in events are sent as null instead of removed
545
555
  socket.current.io.encoder.replacer = (k, v) => (v === undefined ? null : v);
546
556
  socket.current.io.decoder.tryParse = (str) => {
@@ -550,6 +560,18 @@ export const connect = async (
550
560
  return false;
551
561
  }
552
562
  };
563
+ // Set up a reconnect helper function
564
+ socket.current.reconnect = () => {
565
+ if (
566
+ socket.current &&
567
+ !socket.current.connected &&
568
+ !socket.current.wait_connect
569
+ ) {
570
+ socket.current.wait_connect = true;
571
+ socket.current.io.opts.query = { token: getToken() }; // Update token for reconnect.
572
+ socket.current.connect();
573
+ }
574
+ };
553
575
 
554
576
  function checkVisibility() {
555
577
  if (document.visibilityState === "visible") {
@@ -565,7 +587,7 @@ export const connect = async (
565
587
  );
566
588
  } else if (!socket.current.connected) {
567
589
  console.log("Socket is disconnected, attempting to reconnect ");
568
- socket.current.connect();
590
+ socket.current.reconnect();
569
591
  } else {
570
592
  console.log("Socket is reconnected ");
571
593
  }
@@ -588,6 +610,7 @@ export const connect = async (
588
610
 
589
611
  // Once the socket is open, hydrate the page.
590
612
  socket.current.on("connect", async () => {
613
+ socket.current.wait_connect = false;
591
614
  setConnectErrors([]);
592
615
  window.addEventListener("pagehide", pagehideHandler);
593
616
  window.addEventListener("beforeunload", disconnectTrigger);
@@ -599,16 +622,33 @@ export const connect = async (
599
622
  });
600
623
 
601
624
  socket.current.on("connect_error", (error) => {
602
- setConnectErrors((connectErrors) => [connectErrors.slice(-9), error]);
625
+ socket.current.wait_connect = false;
626
+ let n_connect_errors = 0;
627
+ setConnectErrors((connectErrors) => {
628
+ const new_errors = [...connectErrors.slice(-9), error];
629
+ n_connect_errors = new_errors.length;
630
+ return new_errors;
631
+ });
632
+ window.setTimeout(() => {
633
+ if (socket.current && !socket.current.connected) {
634
+ socket.current.reconnect();
635
+ }
636
+ }, 200 * n_connect_errors); // Incremental backoff
603
637
  });
604
638
 
605
639
  // When the socket disconnects reset the event_processing flag
606
- socket.current.on("disconnect", () => {
607
- socket.current = null; // allow reconnect to occur automatically
640
+ socket.current.on("disconnect", (reason, details) => {
641
+ socket.current.wait_connect = false;
642
+ const try_reconnect =
643
+ reason !== "io server disconnect" && reason !== "io client disconnect";
608
644
  event_processing = false;
609
645
  window.removeEventListener("unload", disconnectTrigger);
610
646
  window.removeEventListener("beforeunload", disconnectTrigger);
611
647
  window.removeEventListener("pagehide", pagehideHandler);
648
+ if (try_reconnect) {
649
+ // Attempt to reconnect transient non-intentional disconnects.
650
+ socket.current.reconnect();
651
+ }
612
652
  });
613
653
 
614
654
  // On each received message, queue the updates and events.
@@ -785,6 +825,7 @@ export const useEventLoop = (
785
825
  const [searchParams] = useSearchParams();
786
826
  const [connectErrors, setConnectErrors] = useState([]);
787
827
  const params = useRef(paramsR);
828
+ const mounted = useRef(false);
788
829
 
789
830
  useEffect(() => {
790
831
  const { "*": splat, ...remainingParams } = paramsR;
@@ -796,11 +837,16 @@ export const useEventLoop = (
796
837
  }, [paramsR]);
797
838
 
798
839
  const ensureSocketConnected = useCallback(async () => {
840
+ if (!mounted.current) {
841
+ // During hot reload, some components may still have a reference to
842
+ // addEvents, so avoid reconnecting the socket of an unmounted event loop.
843
+ return;
844
+ }
799
845
  // only use websockets if state is present and backend is not disabled (reflex cloud).
800
846
  if (
801
847
  Object.keys(initialState).length > 1 &&
802
848
  !isBackendDisabled() &&
803
- !socket.current
849
+ !socket.current?.connected
804
850
  ) {
805
851
  // Initialize the websocket connection.
806
852
  await connect(
@@ -813,13 +859,23 @@ export const useEventLoop = (
813
859
  () => params.current,
814
860
  );
815
861
  }
816
- }, [socket, dispatch, setConnectErrors, client_storage, navigate, params]);
862
+ }, [
863
+ socket,
864
+ dispatch,
865
+ setConnectErrors,
866
+ client_storage,
867
+ navigate,
868
+ params,
869
+ mounted,
870
+ ]);
817
871
 
818
872
  // Function to add new events to the event queue.
819
873
  const addEvents = useCallback((events, args, event_actions) => {
820
874
  const _events = events.filter((e) => e !== undefined && e !== null);
821
-
822
- ensureSocketConnected();
875
+ if (!event_actions?.temporal) {
876
+ // Reconnect socket if needed for non-temporal events.
877
+ ensureSocketConnected();
878
+ }
823
879
 
824
880
  if (!(args instanceof Array)) {
825
881
  args = [args];
@@ -914,12 +970,16 @@ export const useEventLoop = (
914
970
  // Handle socket connect/disconnect.
915
971
  useEffect(() => {
916
972
  // Initialize the websocket connection.
973
+ mounted.current = true;
917
974
  ensureSocketConnected();
918
975
 
919
976
  // Cleanup function.
920
977
  return () => {
978
+ mounted.current = false;
921
979
  if (socket.current) {
922
980
  socket.current.disconnect();
981
+ socket.current.off();
982
+ socket.current = null;
923
983
  }
924
984
  };
925
985
  }, []);
reflex/app.py CHANGED
@@ -11,6 +11,7 @@ import functools
11
11
  import inspect
12
12
  import io
13
13
  import json
14
+ import operator
14
15
  import sys
15
16
  import time
16
17
  import traceback
@@ -118,6 +119,7 @@ from reflex.utils.exec import (
118
119
  should_prerender_routes,
119
120
  )
120
121
  from reflex.utils.imports import ImportVar
122
+ from reflex.utils.misc import run_in_thread
121
123
  from reflex.utils.token_manager import TokenManager
122
124
  from reflex.utils.types import ASGIApp, Message, Receive, Scope, Send
123
125
 
@@ -849,7 +851,7 @@ class App(MiddlewareMixin, LifespanMixin):
849
851
 
850
852
  # Setup dynamic args for the route.
851
853
  # this state assignment is only required for tests using the deprecated state kwarg for App
852
- state = self._state if self._state else State
854
+ state = self._state or State
853
855
  state.setup_dynamic_args(get_route_args(route))
854
856
 
855
857
  self._load_events[route] = (
@@ -969,14 +971,10 @@ class App(MiddlewareMixin, LifespanMixin):
969
971
 
970
972
  if admin_dash and admin_dash.models:
971
973
  # Build the admin dashboard
972
- admin = (
973
- admin_dash.admin
974
- if admin_dash.admin
975
- else Admin(
976
- engine=Model.get_db_engine(),
977
- title="Reflex Admin Dashboard",
978
- logo_url="https://reflex.dev/Reflex.svg",
979
- )
974
+ admin = admin_dash.admin or Admin(
975
+ engine=Model.get_db_engine(),
976
+ title="Reflex Admin Dashboard",
977
+ logo_url="https://reflex.dev/Reflex.svg",
980
978
  )
981
979
 
982
980
  for model in admin_dash.models:
@@ -1009,21 +1007,21 @@ class App(MiddlewareMixin, LifespanMixin):
1009
1007
  page_imports = {i for i in page_imports if i not in pinned}
1010
1008
 
1011
1009
  frontend_packages = get_config().frontend_packages
1012
- _frontend_packages = []
1010
+ filtered_frontend_packages = []
1013
1011
  for package in frontend_packages:
1014
1012
  if package in page_imports:
1015
1013
  console.warn(
1016
1014
  f"React packages and their dependencies are inferred from Component.library and Component.lib_dependencies, remove `{package}` from `frontend_packages`"
1017
1015
  )
1018
1016
  continue
1019
- _frontend_packages.append(package)
1020
- page_imports.update(_frontend_packages)
1017
+ filtered_frontend_packages.append(package)
1018
+ page_imports.update(filtered_frontend_packages)
1021
1019
  js_runtimes.install_frontend_packages(page_imports, get_config())
1022
1020
 
1023
1021
  def _app_root(self, app_wrappers: dict[tuple[int, str], Component]) -> Component:
1024
1022
  for component in tuple(app_wrappers.values()):
1025
1023
  app_wrappers.update(component._get_all_app_wrap_components())
1026
- order = sorted(app_wrappers, key=lambda k: k[0], reverse=True)
1024
+ order = sorted(app_wrappers, key=operator.itemgetter(0), reverse=True)
1027
1025
  root = copy.deepcopy(app_wrappers[order[0]])
1028
1026
 
1029
1027
  def reducer(parent: Component, key: tuple[int, str]) -> Component:
@@ -1095,7 +1093,7 @@ class App(MiddlewareMixin, LifespanMixin):
1095
1093
  sticky_badge._add_style_recursive({})
1096
1094
  return sticky_badge
1097
1095
 
1098
- self.app_wraps[(0, "StickyBadge")] = lambda _: memoized_badge()
1096
+ self.app_wraps[0, "StickyBadge"] = lambda _: memoized_badge()
1099
1097
 
1100
1098
  def _apply_decorated_pages(self):
1101
1099
  """Add @rx.page decorated pages to the app."""
@@ -1190,13 +1188,13 @@ class App(MiddlewareMixin, LifespanMixin):
1190
1188
 
1191
1189
  if self.theme is not None:
1192
1190
  # If a theme component was provided, wrap the app with it
1193
- app_wrappers[(20, "Theme")] = self.theme
1191
+ app_wrappers[20, "Theme"] = self.theme
1194
1192
 
1195
1193
  # Get the env mode.
1196
1194
  config = get_config()
1197
1195
 
1198
1196
  if config.react_strict_mode:
1199
- app_wrappers[(200, "StrictMode")] = StrictMode.create()
1197
+ app_wrappers[200, "StrictMode"] = StrictMode.create()
1200
1198
 
1201
1199
  if not should_compile and not dry_run:
1202
1200
  with console.timing("Evaluate Pages (Backend)"):
@@ -1251,7 +1249,7 @@ class App(MiddlewareMixin, LifespanMixin):
1251
1249
  + "\n".join(
1252
1250
  f"{route}: {time * 1000:.1f}ms"
1253
1251
  for route, time in sorted(
1254
- performance_metrics, key=lambda x: x[1], reverse=True
1252
+ performance_metrics, key=operator.itemgetter(1), reverse=True
1255
1253
  )[:10]
1256
1254
  )
1257
1255
  )
@@ -1295,7 +1293,7 @@ class App(MiddlewareMixin, LifespanMixin):
1295
1293
 
1296
1294
  toast_provider = Fragment.create(memoized_toast_provider())
1297
1295
 
1298
- app_wrappers[(44, "ToasterProvider")] = toast_provider
1296
+ app_wrappers[44, "ToasterProvider"] = toast_provider
1299
1297
 
1300
1298
  # Add the app wraps to the app.
1301
1299
  for key, app_wrap in chain(
@@ -1427,12 +1425,10 @@ class App(MiddlewareMixin, LifespanMixin):
1427
1425
  plugin.pre_compile(
1428
1426
  add_save_task=_submit_work_without_advancing,
1429
1427
  add_modify_task=(
1430
- lambda *args, plugin=plugin: modify_files_tasks.append(
1431
- (
1432
- plugin.__class__.__module__ + plugin.__class__.__name__,
1433
- *args,
1434
- )
1435
- )
1428
+ lambda *args, plugin=plugin: modify_files_tasks.append((
1429
+ plugin.__class__.__module__ + plugin.__class__.__name__,
1430
+ *args,
1431
+ ))
1436
1432
  ),
1437
1433
  unevaluated_pages=list(self._unevaluated_pages.values()),
1438
1434
  )
@@ -1552,7 +1548,7 @@ class App(MiddlewareMixin, LifespanMixin):
1552
1548
  if not self._api:
1553
1549
  return
1554
1550
 
1555
- async def all_routes(_request: Request) -> Response:
1551
+ def all_routes(_request: Request) -> Response:
1556
1552
  return JSONResponse(list(self._unevaluated_pages.keys()))
1557
1553
 
1558
1554
  self._api.add_route(
@@ -1663,21 +1659,21 @@ class App(MiddlewareMixin, LifespanMixin):
1663
1659
  strict=True,
1664
1660
  ):
1665
1661
  if hasattr(handler_fn, "__name__"):
1666
- _fn_name = handler_fn.__name__
1662
+ fn_name_ = handler_fn.__name__
1667
1663
  else:
1668
- _fn_name = type(handler_fn).__name__
1664
+ fn_name_ = type(handler_fn).__name__
1669
1665
 
1670
1666
  if isinstance(handler_fn, functools.partial):
1671
- msg = f"Provided custom {handler_domain} exception handler `{_fn_name}` is a partial function. Please provide a named function instead."
1667
+ msg = f"Provided custom {handler_domain} exception handler `{fn_name_}` is a partial function. Please provide a named function instead."
1672
1668
  raise ValueError(msg)
1673
1669
 
1674
1670
  if not callable(handler_fn):
1675
- msg = f"Provided custom {handler_domain} exception handler `{_fn_name}` is not a function."
1671
+ msg = f"Provided custom {handler_domain} exception handler `{fn_name_}` is not a function."
1676
1672
  raise ValueError(msg)
1677
1673
 
1678
1674
  # Allow named functions only as lambda functions cannot be introspected
1679
- if _fn_name == "<lambda>":
1680
- msg = f"Provided custom {handler_domain} exception handler `{_fn_name}` is a lambda function. Please use a named function instead."
1675
+ if fn_name_ == "<lambda>":
1676
+ msg = f"Provided custom {handler_domain} exception handler `{fn_name_}` is a lambda function. Please use a named function instead."
1681
1677
  raise ValueError(msg)
1682
1678
 
1683
1679
  # Check if the function has the necessary annotations and types in the right order
@@ -1690,18 +1686,18 @@ class App(MiddlewareMixin, LifespanMixin):
1690
1686
 
1691
1687
  for required_arg_index, required_arg in enumerate(handler_spec):
1692
1688
  if required_arg not in arg_annotations:
1693
- msg = f"Provided custom {handler_domain} exception handler `{_fn_name}` does not take the required argument `{required_arg}`"
1689
+ msg = f"Provided custom {handler_domain} exception handler `{fn_name_}` does not take the required argument `{required_arg}`"
1694
1690
  raise ValueError(msg)
1695
1691
  if list(arg_annotations.keys())[required_arg_index] != required_arg:
1696
1692
  msg = (
1697
- f"Provided custom {handler_domain} exception handler `{_fn_name}` has the wrong argument order."
1693
+ f"Provided custom {handler_domain} exception handler `{fn_name_}` has the wrong argument order."
1698
1694
  f"Expected `{required_arg}` as the {required_arg_index + 1} argument but got `{list(arg_annotations.keys())[required_arg_index]}`"
1699
1695
  )
1700
1696
  raise ValueError(msg)
1701
1697
 
1702
1698
  if not issubclass(arg_annotations[required_arg], Exception):
1703
1699
  msg = (
1704
- f"Provided custom {handler_domain} exception handler `{_fn_name}` has the wrong type for {required_arg} argument."
1700
+ f"Provided custom {handler_domain} exception handler `{fn_name_}` has the wrong type for {required_arg} argument."
1705
1701
  f"Expected to be `Exception` but got `{arg_annotations[required_arg]}`"
1706
1702
  )
1707
1703
  raise ValueError(msg)
@@ -1725,7 +1721,7 @@ class App(MiddlewareMixin, LifespanMixin):
1725
1721
 
1726
1722
  if not valid:
1727
1723
  msg = (
1728
- f"Provided custom {handler_domain} exception handler `{_fn_name}` has the wrong return type."
1724
+ f"Provided custom {handler_domain} exception handler `{fn_name_}` has the wrong return type."
1729
1725
  f"Expected `EventSpec | list[EventSpec] | None` but got `{return_type}`"
1730
1726
  )
1731
1727
  raise ValueError(msg)
@@ -1754,15 +1750,13 @@ async def process(
1754
1750
  try:
1755
1751
  # Add request data to the state.
1756
1752
  router_data = event.router_data
1757
- router_data.update(
1758
- {
1759
- constants.RouteVar.QUERY: format.format_query_params(event.router_data),
1760
- constants.RouteVar.CLIENT_TOKEN: event.token,
1761
- constants.RouteVar.SESSION_ID: sid,
1762
- constants.RouteVar.HEADERS: headers,
1763
- constants.RouteVar.CLIENT_IP: client_ip,
1764
- }
1765
- )
1753
+ router_data.update({
1754
+ constants.RouteVar.QUERY: format.format_query_params(event.router_data),
1755
+ constants.RouteVar.CLIENT_TOKEN: event.token,
1756
+ constants.RouteVar.SESSION_ID: sid,
1757
+ constants.RouteVar.HEADERS: headers,
1758
+ constants.RouteVar.CLIENT_IP: client_ip,
1759
+ })
1766
1760
  # Get the state for the session exclusively.
1767
1761
  async with app.state_manager.modify_state(event.substate_token) as state:
1768
1762
  # When this is a brand new instance of the state, signal the
@@ -1781,16 +1775,16 @@ async def process(
1781
1775
  name=f"reflex_emit_reload|{event.name}|{time.time()}|{event.token}",
1782
1776
  )
1783
1777
  return
1778
+ router_data[constants.RouteVar.PATH] = "/" + (
1779
+ app.router(path) or "404"
1780
+ if (path := router_data.get(constants.RouteVar.PATH))
1781
+ else "404"
1782
+ ).removeprefix("/")
1784
1783
  # re-assign only when the value is different
1785
1784
  if state.router_data != router_data:
1786
1785
  # assignment will recurse into substates and force recalculation of
1787
1786
  # dependent ComputedVar (dynamic route variables)
1788
1787
  state.router_data = router_data
1789
- router_data[constants.RouteVar.PATH] = "/" + (
1790
- app.router(path) or "404"
1791
- if (path := router_data.get(constants.RouteVar.PATH))
1792
- else "404"
1793
- ).removeprefix("/")
1794
1788
  state.router = RouterData.from_router_data(router_data)
1795
1789
 
1796
1790
  # Preprocess the event.
@@ -1820,7 +1814,7 @@ async def process(
1820
1814
  raise
1821
1815
 
1822
1816
 
1823
- async def ping(_request: Request) -> Response:
1817
+ def ping(_request: Request) -> Response:
1824
1818
  """Test API endpoint.
1825
1819
 
1826
1820
  Args:
@@ -1852,7 +1846,7 @@ async def health(_request: Request) -> JSONResponse:
1852
1846
  if prerequisites.check_db_used():
1853
1847
  from reflex.model import get_db_status
1854
1848
 
1855
- tasks.append(get_db_status())
1849
+ tasks.append(run_in_thread(get_db_status))
1856
1850
  if prerequisites.check_redis_used():
1857
1851
  tasks.append(prerequisites.get_redis_status())
1858
1852
 
@@ -41,13 +41,13 @@ class LifespanMixin(AppMixin):
41
41
  signature = inspect.signature(task)
42
42
  if "app" in signature.parameters:
43
43
  task = functools.partial(task, app=app)
44
- _t = task()
45
- if isinstance(_t, contextlib._AsyncGeneratorContextManager):
46
- await stack.enter_async_context(_t)
44
+ t_ = task()
45
+ if isinstance(t_, contextlib._AsyncGeneratorContextManager):
46
+ await stack.enter_async_context(t_)
47
47
  console.debug(run_msg.format(type="asynccontextmanager"))
48
- elif isinstance(_t, Coroutine):
48
+ elif isinstance(t_, Coroutine):
49
49
  task_ = asyncio.create_task(
50
- _t,
50
+ t_,
51
51
  name=f"reflex_lifespan_task|{task_name}|{time.time()}",
52
52
  )
53
53
  task_.add_done_callback(lambda t: t.result())
@@ -71,6 +71,13 @@ class LifespanMixin(AppMixin):
71
71
  await event_namespace._token_manager.disconnect_all()
72
72
  except Exception as e:
73
73
  console.error(f"Error during lifespan cleanup: {e}")
74
+ # Flush any pending writes from the state manager.
75
+ try:
76
+ state_manager = self.state_manager # pyright: ignore[reportAttributeAccessIssue]
77
+ except AttributeError:
78
+ pass
79
+ else:
80
+ await state_manager.close()
74
81
 
75
82
  def register_lifespan_task(self, task: Callable | asyncio.Task, **task_kwargs):
76
83
  """Register a task to run during the lifespan of the app.
reflex/base.py CHANGED
@@ -48,6 +48,7 @@ if find_spec("pydantic") and find_spec("pydantic.v1"):
48
48
  self.dict(),
49
49
  default=serialize,
50
50
  )
51
+
51
52
  else:
52
53
 
53
54
  class PydanticNotFoundFallback:
@@ -758,12 +758,10 @@ def into_component(component: Component | ComponentCallable) -> Component:
758
758
  raise TypeError(
759
759
  "Cannot pass a Var to a built-in function. Consider using .length() for accessing the length of an iterable Var."
760
760
  ).with_traceback(e.__traceback__) from None
761
- if message.endswith(
762
- (
763
- "indices must be integers or slices, not NumberCastedVar",
764
- "indices must be integers or slices, not BooleanCastedVar",
765
- )
766
- ):
761
+ if message.endswith((
762
+ "indices must be integers or slices, not NumberCastedVar",
763
+ "indices must be integers or slices, not BooleanCastedVar",
764
+ )):
767
765
  raise TypeError(
768
766
  "Cannot index into a primitive sequence with a Var. Consider calling rx.Var.create() on the sequence."
769
767
  ).with_traceback(e.__traceback__) from None
@@ -83,9 +83,9 @@ class _RenderUtils:
83
83
 
84
84
  @staticmethod
85
85
  def render_iterable_tag(component: Any) -> str:
86
- children_rendered = "".join(
87
- [_RenderUtils.render(child) for child in component.get("children", [])]
88
- )
86
+ children_rendered = "".join([
87
+ _RenderUtils.render(child) for child in component.get("children", [])
88
+ ])
89
89
  return f"Array.prototype.map.call({component['iterable_state']} ?? [],(({component['arg_name']},{component['arg_index']})=>({children_rendered})))"
90
90
 
91
91
  @staticmethod
@@ -189,16 +189,14 @@ def app_root_template(
189
189
 
190
190
  custom_code_str = "\n".join(custom_codes)
191
191
 
192
- import_window_libraries = "\n".join(
193
- [
194
- f'import * as {lib_alias} from "{lib_path}";'
195
- for lib_alias, lib_path in window_libraries
196
- ]
197
- )
192
+ import_window_libraries = "\n".join([
193
+ f'import * as {lib_alias} from "{lib_path}";'
194
+ for lib_alias, lib_path in window_libraries
195
+ ])
198
196
 
199
- window_imports_str = "\n".join(
200
- [f' "{lib_path}": {lib_alias},' for lib_alias, lib_path in window_libraries]
201
- )
197
+ window_imports_str = "\n".join([
198
+ f' "{lib_path}": {lib_alias},' for lib_alias, lib_path in window_libraries
199
+ ])
202
200
 
203
201
  return f"""
204
202
  {imports_str}
@@ -277,12 +275,10 @@ def context_template(
277
275
  Rendered context file content as string.
278
276
  """
279
277
  initial_state = initial_state or {}
280
- state_contexts_str = "".join(
281
- [
282
- f"{format_state_name(state_name)}: createContext(null),"
283
- for state_name in initial_state
284
- ]
285
- )
278
+ state_contexts_str = "".join([
279
+ f"{format_state_name(state_name)}: createContext(null),"
280
+ for state_name in initial_state
281
+ ])
286
282
 
287
283
  state_str = (
288
284
  rf"""
@@ -486,16 +482,14 @@ def package_json_template(
486
482
  Returns:
487
483
  Rendered package.json content as string.
488
484
  """
489
- return json.dumps(
490
- {
491
- "name": "reflex",
492
- "type": "module",
493
- "scripts": scripts,
494
- "dependencies": dependencies,
495
- "devDependencies": dev_dependencies,
496
- "overrides": overrides,
497
- }
498
- )
485
+ return json.dumps({
486
+ "name": "reflex",
487
+ "type": "module",
488
+ "scripts": scripts,
489
+ "dependencies": dependencies,
490
+ "devDependencies": dev_dependencies,
491
+ "overrides": overrides,
492
+ })
499
493
 
500
494
 
501
495
  def vite_config_template(base: str, hmr: bool, force_full_reload: bool):
@@ -693,9 +687,9 @@ def styles_template(stylesheets: list[str]) -> str:
693
687
  Returns:
694
688
  Rendered styles.css content as string.
695
689
  """
696
- return "@layer __reflex_base;\n" + "\n".join(
697
- [f"@import url('{sheet_name}');" for sheet_name in stylesheets]
698
- )
690
+ return "@layer __reflex_base;\n" + "\n".join([
691
+ f"@import url('{sheet_name}');" for sheet_name in stylesheets
692
+ ])
699
693
 
700
694
 
701
695
  def _render_hooks(hooks: dict[str, VarData | None], memo: list | None = None) -> str:
reflex/compiler/utils.py CHANGED
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import concurrent.futures
7
+ import operator
7
8
  import traceback
8
9
  from collections.abc import Mapping, Sequence
9
10
  from datetime import datetime
@@ -161,7 +162,7 @@ def get_import_dict(
161
162
  return _ImportDict(
162
163
  lib=lib,
163
164
  default=default,
164
- rest=rest if rest else [],
165
+ rest=rest or [],
165
166
  )
166
167
 
167
168
 
@@ -190,7 +191,7 @@ def _sorted_keys(d: Mapping[str, Any]) -> dict[str, Any]:
190
191
  Returns:
191
192
  A new dictionary with sorted keys.
192
193
  """
193
- return dict(sorted(d.items(), key=lambda kv: kv[0]))
194
+ return dict(sorted(d.items(), key=operator.itemgetter(0)))
194
195
 
195
196
 
196
197
  def compile_state(state: type[BaseState]) -> dict:
@@ -442,9 +443,9 @@ def create_theme(style: ComponentStyle) -> dict:
442
443
 
443
444
  root_style = {
444
445
  # Root styles.
445
- ":root": Style(
446
- {f"*{k}": v for k, v in style_rules.items() if k.startswith(":")}
447
- ),
446
+ ":root": Style({
447
+ f"*{k}": v for k, v in style_rules.items() if k.startswith(":")
448
+ }),
448
449
  # Body styles.
449
450
  "body": Style(
450
451
  {k: v for k, v in style_rules.items() if not k.startswith(":")},