reflex 0.7.13a2__py3-none-any.whl → 0.7.14__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 (261) hide show
  1. reflex/.templates/apps/blank/code/blank.py +0 -2
  2. reflex/app.py +85 -89
  3. reflex/app_mixins/lifespan.py +2 -3
  4. reflex/app_mixins/middleware.py +1 -0
  5. reflex/app_mixins/mixin.py +0 -1
  6. reflex/assets.py +7 -4
  7. reflex/base.py +3 -2
  8. reflex/compiler/compiler.py +79 -65
  9. reflex/compiler/utils.py +8 -6
  10. reflex/components/base/app_wrap.pyi +0 -1
  11. reflex/components/base/bare.py +22 -12
  12. reflex/components/base/body.pyi +0 -1
  13. reflex/components/base/document.pyi +0 -5
  14. reflex/components/base/error_boundary.pyi +0 -1
  15. reflex/components/base/fragment.pyi +0 -1
  16. reflex/components/base/head.pyi +0 -2
  17. reflex/components/base/link.pyi +0 -2
  18. reflex/components/base/meta.py +2 -1
  19. reflex/components/base/meta.pyi +0 -4
  20. reflex/components/base/script.py +2 -1
  21. reflex/components/base/script.pyi +0 -1
  22. reflex/components/base/strict_mode.pyi +0 -1
  23. reflex/components/component.py +85 -45
  24. reflex/components/core/auto_scroll.pyi +0 -1
  25. reflex/components/core/banner.py +1 -1
  26. reflex/components/core/banner.pyi +0 -6
  27. reflex/components/core/breakpoints.py +9 -11
  28. reflex/components/core/client_side_routing.pyi +0 -2
  29. reflex/components/core/clipboard.pyi +0 -1
  30. reflex/components/core/colors.py +10 -7
  31. reflex/components/core/cond.py +4 -2
  32. reflex/components/core/debounce.py +5 -3
  33. reflex/components/core/debounce.pyi +0 -1
  34. reflex/components/core/foreach.py +8 -6
  35. reflex/components/core/html.py +3 -3
  36. reflex/components/core/html.pyi +0 -1
  37. reflex/components/core/match.py +19 -17
  38. reflex/components/core/sticky.pyi +0 -4
  39. reflex/components/core/upload.py +1 -1
  40. reflex/components/core/upload.pyi +0 -5
  41. reflex/components/datadisplay/code.py +1 -2
  42. reflex/components/datadisplay/code.pyi +0 -2
  43. reflex/components/datadisplay/dataeditor.py +7 -10
  44. reflex/components/datadisplay/dataeditor.pyi +0 -1
  45. reflex/components/datadisplay/logo.py +3 -4
  46. reflex/components/datadisplay/shiki_code_block.py +8 -11
  47. reflex/components/datadisplay/shiki_code_block.pyi +0 -3
  48. reflex/components/dynamic.py +2 -3
  49. reflex/components/el/__init__.pyi +2 -0
  50. reflex/components/el/element.pyi +0 -1
  51. reflex/components/el/elements/__init__.py +1 -0
  52. reflex/components/el/elements/__init__.pyi +3 -0
  53. reflex/components/el/elements/base.pyi +0 -1
  54. reflex/components/el/elements/forms.py +14 -15
  55. reflex/components/el/elements/forms.pyi +15 -32
  56. reflex/components/el/elements/inline.pyi +0 -28
  57. reflex/components/el/elements/media.py +26 -0
  58. reflex/components/el/elements/media.pyi +259 -25
  59. reflex/components/el/elements/metadata.py +0 -1
  60. reflex/components/el/elements/metadata.pyi +0 -6
  61. reflex/components/el/elements/other.pyi +0 -7
  62. reflex/components/el/elements/scripts.pyi +0 -3
  63. reflex/components/el/elements/sectioning.pyi +0 -15
  64. reflex/components/el/elements/tables.pyi +0 -10
  65. reflex/components/el/elements/typography.pyi +0 -15
  66. reflex/components/gridjs/datatable.py +10 -13
  67. reflex/components/gridjs/datatable.pyi +0 -2
  68. reflex/components/lucide/icon.py +10 -9
  69. reflex/components/lucide/icon.pyi +0 -3
  70. reflex/components/markdown/markdown.py +6 -8
  71. reflex/components/markdown/markdown.pyi +0 -1
  72. reflex/components/moment/moment.pyi +0 -1
  73. reflex/components/next/base.py +0 -2
  74. reflex/components/next/base.pyi +0 -3
  75. reflex/components/next/image.pyi +0 -1
  76. reflex/components/next/link.pyi +0 -1
  77. reflex/components/next/video.pyi +0 -1
  78. reflex/components/plotly/plotly.pyi +0 -9
  79. reflex/components/props.py +4 -3
  80. reflex/components/radix/primitives/accordion.pyi +0 -7
  81. reflex/components/radix/primitives/base.py +1 -3
  82. reflex/components/radix/primitives/base.pyi +0 -2
  83. reflex/components/radix/primitives/drawer.pyi +0 -11
  84. reflex/components/radix/primitives/form.py +4 -8
  85. reflex/components/radix/primitives/form.pyi +0 -12
  86. reflex/components/radix/primitives/progress.py +1 -1
  87. reflex/components/radix/primitives/progress.pyi +0 -5
  88. reflex/components/radix/primitives/slider.py +1 -1
  89. reflex/components/radix/primitives/slider.pyi +0 -5
  90. reflex/components/radix/themes/base.pyi +0 -8
  91. reflex/components/radix/themes/color_mode.pyi +0 -3
  92. reflex/components/radix/themes/components/alert_dialog.py +4 -2
  93. reflex/components/radix/themes/components/alert_dialog.pyi +4 -9
  94. reflex/components/radix/themes/components/aspect_ratio.py +1 -2
  95. reflex/components/radix/themes/components/aspect_ratio.pyi +1 -3
  96. reflex/components/radix/themes/components/avatar.py +5 -2
  97. reflex/components/radix/themes/components/avatar.pyi +1 -3
  98. reflex/components/radix/themes/components/badge.py +5 -2
  99. reflex/components/radix/themes/components/badge.pyi +1 -3
  100. reflex/components/radix/themes/components/button.py +2 -3
  101. reflex/components/radix/themes/components/button.pyi +1 -3
  102. reflex/components/radix/themes/components/callout.py +1 -2
  103. reflex/components/radix/themes/components/callout.pyi +1 -7
  104. reflex/components/radix/themes/components/card.py +1 -2
  105. reflex/components/radix/themes/components/card.pyi +1 -3
  106. reflex/components/radix/themes/components/checkbox.py +7 -4
  107. reflex/components/radix/themes/components/checkbox.pyi +1 -5
  108. reflex/components/radix/themes/components/checkbox_cards.py +1 -2
  109. reflex/components/radix/themes/components/checkbox_cards.pyi +1 -4
  110. reflex/components/radix/themes/components/checkbox_group.py +1 -2
  111. reflex/components/radix/themes/components/checkbox_group.pyi +1 -4
  112. reflex/components/radix/themes/components/context_menu.py +1 -1
  113. reflex/components/radix/themes/components/context_menu.pyi +1 -14
  114. reflex/components/radix/themes/components/data_list.py +1 -2
  115. reflex/components/radix/themes/components/data_list.pyi +1 -6
  116. reflex/components/radix/themes/components/dialog.py +4 -2
  117. reflex/components/radix/themes/components/dialog.pyi +4 -9
  118. reflex/components/radix/themes/components/dropdown_menu.py +5 -2
  119. reflex/components/radix/themes/components/dropdown_menu.pyi +4 -10
  120. reflex/components/radix/themes/components/hover_card.py +4 -2
  121. reflex/components/radix/themes/components/hover_card.pyi +4 -6
  122. reflex/components/radix/themes/components/icon_button.py +7 -8
  123. reflex/components/radix/themes/components/icon_button.pyi +1 -3
  124. reflex/components/radix/themes/components/inset.py +1 -2
  125. reflex/components/radix/themes/components/inset.pyi +1 -3
  126. reflex/components/radix/themes/components/popover.py +4 -2
  127. reflex/components/radix/themes/components/popover.pyi +4 -6
  128. reflex/components/radix/themes/components/progress.py +1 -2
  129. reflex/components/radix/themes/components/progress.pyi +1 -3
  130. reflex/components/radix/themes/components/radio.py +1 -2
  131. reflex/components/radix/themes/components/radio.pyi +1 -3
  132. reflex/components/radix/themes/components/radio_cards.py +1 -2
  133. reflex/components/radix/themes/components/radio_cards.pyi +1 -4
  134. reflex/components/radix/themes/components/radio_group.py +7 -5
  135. reflex/components/radix/themes/components/radio_group.pyi +1 -6
  136. reflex/components/radix/themes/components/scroll_area.py +1 -2
  137. reflex/components/radix/themes/components/scroll_area.pyi +1 -3
  138. reflex/components/radix/themes/components/segmented_control.py +1 -2
  139. reflex/components/radix/themes/components/segmented_control.pyi +1 -4
  140. reflex/components/radix/themes/components/select.py +5 -2
  141. reflex/components/radix/themes/components/select.pyi +1 -11
  142. reflex/components/radix/themes/components/separator.py +1 -2
  143. reflex/components/radix/themes/components/separator.pyi +1 -3
  144. reflex/components/radix/themes/components/skeleton.py +1 -2
  145. reflex/components/radix/themes/components/skeleton.pyi +1 -3
  146. reflex/components/radix/themes/components/slider.py +1 -2
  147. reflex/components/radix/themes/components/slider.pyi +1 -3
  148. reflex/components/radix/themes/components/spinner.py +1 -2
  149. reflex/components/radix/themes/components/spinner.pyi +1 -3
  150. reflex/components/radix/themes/components/switch.py +1 -2
  151. reflex/components/radix/themes/components/switch.pyi +1 -3
  152. reflex/components/radix/themes/components/table.py +1 -2
  153. reflex/components/radix/themes/components/table.pyi +1 -9
  154. reflex/components/radix/themes/components/tabs.py +1 -2
  155. reflex/components/radix/themes/components/tabs.pyi +1 -7
  156. reflex/components/radix/themes/components/text_area.py +5 -2
  157. reflex/components/radix/themes/components/text_area.pyi +2 -4
  158. reflex/components/radix/themes/components/text_field.py +5 -2
  159. reflex/components/radix/themes/components/text_field.pyi +1 -5
  160. reflex/components/radix/themes/components/tooltip.py +1 -2
  161. reflex/components/radix/themes/components/tooltip.pyi +1 -3
  162. reflex/components/radix/themes/layout/base.py +5 -2
  163. reflex/components/radix/themes/layout/base.pyi +5 -3
  164. reflex/components/radix/themes/layout/box.py +1 -2
  165. reflex/components/radix/themes/layout/box.pyi +1 -3
  166. reflex/components/radix/themes/layout/center.pyi +0 -1
  167. reflex/components/radix/themes/layout/container.py +1 -2
  168. reflex/components/radix/themes/layout/container.pyi +1 -3
  169. reflex/components/radix/themes/layout/flex.py +6 -2
  170. reflex/components/radix/themes/layout/flex.pyi +1 -3
  171. reflex/components/radix/themes/layout/grid.py +6 -2
  172. reflex/components/radix/themes/layout/grid.pyi +1 -3
  173. reflex/components/radix/themes/layout/list.py +2 -1
  174. reflex/components/radix/themes/layout/list.pyi +0 -5
  175. reflex/components/radix/themes/layout/section.py +1 -2
  176. reflex/components/radix/themes/layout/section.pyi +1 -3
  177. reflex/components/radix/themes/layout/spacer.pyi +0 -1
  178. reflex/components/radix/themes/layout/stack.py +1 -1
  179. reflex/components/radix/themes/layout/stack.pyi +0 -3
  180. reflex/components/radix/themes/typography/blockquote.py +1 -1
  181. reflex/components/radix/themes/typography/blockquote.pyi +1 -3
  182. reflex/components/radix/themes/typography/code.py +5 -1
  183. reflex/components/radix/themes/typography/code.pyi +1 -3
  184. reflex/components/radix/themes/typography/heading.py +1 -1
  185. reflex/components/radix/themes/typography/heading.pyi +1 -3
  186. reflex/components/radix/themes/typography/link.py +3 -2
  187. reflex/components/radix/themes/typography/link.pyi +1 -3
  188. reflex/components/radix/themes/typography/text.py +1 -1
  189. reflex/components/radix/themes/typography/text.pyi +1 -9
  190. reflex/components/react_player/audio.py +0 -2
  191. reflex/components/react_player/audio.pyi +0 -3
  192. reflex/components/react_player/react_player.pyi +0 -1
  193. reflex/components/react_player/video.py +0 -2
  194. reflex/components/react_player/video.pyi +0 -3
  195. reflex/components/recharts/__init__.py +1 -1
  196. reflex/components/recharts/__init__.pyi +1 -1
  197. reflex/components/recharts/cartesian.py +20 -25
  198. reflex/components/recharts/cartesian.pyi +20 -37
  199. reflex/components/recharts/charts.py +2 -1
  200. reflex/components/recharts/charts.pyi +0 -12
  201. reflex/components/recharts/general.pyi +0 -6
  202. reflex/components/recharts/polar.py +5 -4
  203. reflex/components/recharts/polar.pyi +4 -10
  204. reflex/components/recharts/recharts.py +12 -10
  205. reflex/components/recharts/recharts.pyi +10 -11
  206. reflex/components/sonner/toast.py +2 -2
  207. reflex/components/sonner/toast.pyi +0 -2
  208. reflex/components/suneditor/editor.py +2 -1
  209. reflex/components/suneditor/editor.pyi +0 -1
  210. reflex/components/tags/iter_tag.py +4 -2
  211. reflex/config.py +41 -615
  212. reflex/constants/base.py +6 -6
  213. reflex/constants/compiler.py +8 -6
  214. reflex/constants/installer.py +25 -16
  215. reflex/custom_components/custom_components.py +1 -2
  216. reflex/environment.py +606 -0
  217. reflex/event.py +58 -60
  218. reflex/experimental/__init__.py +2 -2
  219. reflex/experimental/client_state.py +9 -4
  220. reflex/experimental/layout.pyi +0 -5
  221. reflex/istate/manager.py +17 -20
  222. reflex/istate/proxy.py +19 -12
  223. reflex/model.py +8 -5
  224. reflex/plugins/base.py +8 -0
  225. reflex/plugins/tailwind_v3.py +8 -0
  226. reflex/plugins/tailwind_v4.py +8 -0
  227. reflex/reflex.py +11 -12
  228. reflex/route.py +7 -9
  229. reflex/state.py +67 -71
  230. reflex/style.py +3 -1
  231. reflex/testing.py +49 -30
  232. reflex/utils/build.py +2 -1
  233. reflex/utils/console.py +70 -17
  234. reflex/utils/exec.py +113 -39
  235. reflex/utils/export.py +2 -1
  236. reflex/utils/format.py +21 -24
  237. reflex/utils/imports.py +4 -3
  238. reflex/utils/lazy_loader.py +3 -3
  239. reflex/utils/misc.py +2 -1
  240. reflex/utils/net.py +2 -2
  241. reflex/utils/path_ops.py +4 -2
  242. reflex/utils/prerequisites.py +69 -39
  243. reflex/utils/processes.py +5 -7
  244. reflex/utils/pyi_generator.py +46 -41
  245. reflex/utils/redir.py +1 -1
  246. reflex/utils/registry.py +1 -1
  247. reflex/utils/serializers.py +4 -4
  248. reflex/utils/telemetry.py +36 -3
  249. reflex/utils/types.py +16 -13
  250. reflex/vars/base.py +96 -109
  251. reflex/vars/datetime.py +2 -1
  252. reflex/vars/dep_tracking.py +19 -28
  253. reflex/vars/number.py +6 -7
  254. reflex/vars/object.py +5 -6
  255. reflex/vars/sequence.py +11 -11
  256. {reflex-0.7.13a2.dist-info → reflex-0.7.14.dist-info}/METADATA +1 -1
  257. reflex-0.7.14.dist-info/RECORD +408 -0
  258. reflex-0.7.13a2.dist-info/RECORD +0 -407
  259. {reflex-0.7.13a2.dist-info → reflex-0.7.14.dist-info}/WHEEL +0 -0
  260. {reflex-0.7.13a2.dist-info → reflex-0.7.14.dist-info}/entry_points.txt +0 -0
  261. {reflex-0.7.13a2.dist-info → reflex-0.7.14.dist-info}/licenses/LICENSE +0 -0
reflex/testing.py CHANGED
@@ -28,6 +28,7 @@ import psutil
28
28
  import uvicorn
29
29
 
30
30
  import reflex
31
+ import reflex.environment
31
32
  import reflex.reflex
32
33
  import reflex.utils.build
33
34
  import reflex.utils.exec
@@ -35,7 +36,8 @@ import reflex.utils.format
35
36
  import reflex.utils.prerequisites
36
37
  import reflex.utils.processes
37
38
  from reflex.components.component import CustomComponent
38
- from reflex.config import environment, get_config
39
+ from reflex.config import get_config
40
+ from reflex.environment import environment
39
41
  from reflex.state import (
40
42
  BaseState,
41
43
  StateManager,
@@ -155,9 +157,8 @@ class AppHarness:
155
157
  app_name = f"{func_name}_{slug_suffix}"
156
158
  app_name = re.sub(r"[^a-zA-Z0-9_]", "_", app_name)
157
159
  elif isinstance(app_source, str):
158
- raise ValueError(
159
- "app_name must be provided when app_source is a string."
160
- )
160
+ msg = "app_name must be provided when app_source is a string."
161
+ raise ValueError(msg)
161
162
  else:
162
163
  app_name = app_source.__name__
163
164
 
@@ -285,7 +286,8 @@ class AppHarness:
285
286
  self.app_instance._state_manager, StateManagerRedis
286
287
  ):
287
288
  if self.app_instance._state is None:
288
- raise RuntimeError("State is not set.")
289
+ msg = "State is not set."
290
+ raise RuntimeError(msg)
289
291
  # Create our own redis connection for testing.
290
292
  self.state_manager = StateManagerRedis.create(self.app_instance._state)
291
293
  else:
@@ -299,7 +301,8 @@ class AppHarness:
299
301
 
300
302
  def _get_backend_shutdown_handler(self):
301
303
  if self.backend is None:
302
- raise RuntimeError("Backend was not initialized.")
304
+ msg = "Backend was not initialized."
305
+ raise RuntimeError(msg)
303
306
 
304
307
  original_shutdown = self.backend.shutdown
305
308
 
@@ -330,7 +333,8 @@ class AppHarness:
330
333
 
331
334
  def _start_backend(self, port: int = 0):
332
335
  if self.app_asgi is None:
333
- raise RuntimeError("App was not initialized.")
336
+ msg = "App was not initialized."
337
+ raise RuntimeError(msg)
334
338
  self.backend = uvicorn.Server(
335
339
  uvicorn.Config(
336
340
  app=self.app_asgi,
@@ -366,7 +370,8 @@ class AppHarness:
366
370
  state=self.app_instance._state,
367
371
  )
368
372
  if not isinstance(self.app_instance.state_manager, StateManagerRedis):
369
- raise RuntimeError("Failed to reset state manager.")
373
+ msg = "Failed to reset state manager."
374
+ raise RuntimeError(msg)
370
375
 
371
376
  def _start_frontend(self):
372
377
  # Set up the frontend.
@@ -406,7 +411,8 @@ class AppHarness:
406
411
  config.deploy_url = self.frontend_url
407
412
  break
408
413
  if self.frontend_url is None:
409
- raise RuntimeError("Frontend did not start")
414
+ msg = "Frontend did not start"
415
+ raise RuntimeError(msg)
410
416
 
411
417
  def consume_frontend_output():
412
418
  while True:
@@ -578,20 +584,23 @@ class AppHarness:
578
584
  TimeoutError: when server or sockets are not ready
579
585
  """
580
586
  if self.backend is None:
581
- raise RuntimeError("Backend is not running.")
587
+ msg = "Backend is not running."
588
+ raise RuntimeError(msg)
582
589
  backend = self.backend
583
590
  # check for servers to be initialized
584
591
  if not self._poll_for(
585
592
  target=lambda: getattr(backend, "servers", False),
586
593
  timeout=timeout,
587
594
  ):
588
- raise TimeoutError("Backend servers are not initialized.")
595
+ msg = "Backend servers are not initialized."
596
+ raise TimeoutError(msg)
589
597
  # check for sockets to be listening
590
598
  if not self._poll_for(
591
599
  target=lambda: getattr(backend.servers[0], "sockets", False),
592
600
  timeout=timeout,
593
601
  ):
594
- raise TimeoutError("Backend is not listening.")
602
+ msg = "Backend is not listening."
603
+ raise TimeoutError(msg)
595
604
  return backend.servers[0].sockets[0]
596
605
 
597
606
  def frontend(
@@ -619,12 +628,14 @@ class AppHarness:
619
628
  RuntimeError: when selenium is not importable or frontend is not running
620
629
  """
621
630
  if not has_selenium:
622
- raise RuntimeError(
631
+ msg = (
623
632
  "Frontend functionality requires `selenium` to be installed, "
624
633
  "and it could not be imported."
625
634
  )
635
+ raise RuntimeError(msg)
626
636
  if self.frontend_url is None:
627
- raise RuntimeError("Frontend is not running.")
637
+ msg = "Frontend is not running."
638
+ raise RuntimeError(msg)
628
639
  want_headless = False
629
640
  if environment.APP_HARNESS_HEADLESS.get():
630
641
  want_headless = True
@@ -650,7 +661,8 @@ class AppHarness:
650
661
  if want_headless:
651
662
  driver_options.add_argument("headless")
652
663
  if driver_options is None:
653
- raise RuntimeError(f"Could not determine options for {driver_clz}")
664
+ msg = f"Could not determine options for {driver_clz}"
665
+ raise RuntimeError(msg)
654
666
  if args := environment.APP_HARNESS_DRIVER_ARGS.get():
655
667
  for arg in args.split(","):
656
668
  driver_options.add_argument(arg)
@@ -680,7 +692,8 @@ class AppHarness:
680
692
  RuntimeError: when the app hasn't started running
681
693
  """
682
694
  if self.state_manager is None:
683
- raise RuntimeError("state_manager is not set.")
695
+ msg = "state_manager is not set."
696
+ raise RuntimeError(msg)
684
697
  try:
685
698
  return await self.state_manager.get_state(token)
686
699
  finally:
@@ -698,7 +711,8 @@ class AppHarness:
698
711
  RuntimeError: when the app hasn't started running
699
712
  """
700
713
  if self.state_manager is None:
701
- raise RuntimeError("state_manager is not set.")
714
+ msg = "state_manager is not set."
715
+ raise RuntimeError(msg)
702
716
  state = await self.get_state(token)
703
717
  for key, value in kwargs.items():
704
718
  setattr(state, key, value)
@@ -722,9 +736,11 @@ class AppHarness:
722
736
  RuntimeError: when the app hasn't started running
723
737
  """
724
738
  if self.state_manager is None:
725
- raise RuntimeError("state_manager is not set.")
739
+ msg = "state_manager is not set."
740
+ raise RuntimeError(msg)
726
741
  if self.app_instance is None:
727
- raise RuntimeError("App is not running.")
742
+ msg = "App is not running."
743
+ raise RuntimeError(msg)
728
744
  app_state_manager = self.app_instance.state_manager
729
745
  if isinstance(self.state_manager, StateManagerRedis):
730
746
  # Temporarily replace the app's state manager with our own, since
@@ -761,9 +777,8 @@ class AppHarness:
761
777
  target=lambda: element.text != exp_not_equal,
762
778
  timeout=timeout,
763
779
  ):
764
- raise TimeoutError(
765
- f"{element} content remains {exp_not_equal!r} while polling.",
766
- )
780
+ msg = f"{element} content remains {exp_not_equal!r} while polling."
781
+ raise TimeoutError(msg)
767
782
  return element.text
768
783
 
769
784
  def poll_for_value(
@@ -792,9 +807,8 @@ class AppHarness:
792
807
  target=lambda: element.get_attribute("value") not in exp_not_equal,
793
808
  timeout=timeout,
794
809
  ):
795
- raise TimeoutError(
796
- f"{element} content remains {exp_not_equal!r} while polling.",
797
- )
810
+ msg = f"{element} content remains {exp_not_equal!r} while polling."
811
+ raise TimeoutError(msg)
798
812
  return element.get_attribute("value")
799
813
 
800
814
  def poll_for_clients(self, timeout: TimeoutType = None) -> dict[str, BaseState]:
@@ -812,15 +826,18 @@ class AppHarness:
812
826
  ValueError: when the state_manager is not a memory state manager
813
827
  """
814
828
  if self.app_instance is None:
815
- raise RuntimeError("App is not running.")
829
+ msg = "App is not running."
830
+ raise RuntimeError(msg)
816
831
  state_manager = self.app_instance.state_manager
817
832
  if not isinstance(state_manager, (StateManagerMemory, StateManagerDisk)):
818
- raise ValueError("Only works with memory or disk state manager")
833
+ msg = "Only works with memory or disk state manager"
834
+ raise ValueError(msg)
819
835
  if not self._poll_for(
820
836
  target=lambda: state_manager.states,
821
837
  timeout=timeout,
822
838
  ):
823
- raise TimeoutError("No states were observed while polling.")
839
+ msg = "No states were observed while polling."
840
+ raise TimeoutError(msg)
824
841
  return state_manager.states
825
842
 
826
843
 
@@ -962,11 +979,13 @@ class AppHarnessProd(AppHarness):
962
979
  def _wait_frontend(self):
963
980
  self._poll_for(lambda: self.frontend_server is not None)
964
981
  if self.frontend_server is None or not self.frontend_server.socket.fileno():
965
- raise RuntimeError("Frontend did not start")
982
+ msg = "Frontend did not start"
983
+ raise RuntimeError(msg)
966
984
 
967
985
  def _start_backend(self):
968
986
  if self.app_asgi is None:
969
- raise RuntimeError("App was not initialized.")
987
+ msg = "App was not initialized."
988
+ raise RuntimeError(msg)
970
989
  environment.REFLEX_SKIP_COMPILE.set(True)
971
990
  self.backend = uvicorn.Server(
972
991
  uvicorn.Config(
reflex/utils/build.py CHANGED
@@ -132,7 +132,7 @@ def _zip(
132
132
  def zip_app(
133
133
  frontend: bool = True,
134
134
  backend: bool = True,
135
- zip_dest_dir: str | Path = Path.cwd(),
135
+ zip_dest_dir: str | Path | None = None,
136
136
  upload_db_file: bool = False,
137
137
  ):
138
138
  """Zip up the app.
@@ -143,6 +143,7 @@ def zip_app(
143
143
  zip_dest_dir: The directory to export the zip file to.
144
144
  upload_db_file: Whether to upload the database file.
145
145
  """
146
+ zip_dest_dir = zip_dest_dir or Path.cwd()
146
147
  zip_dest_dir = Path(zip_dest_dir)
147
148
  files_to_exclude = {
148
149
  constants.ComponentName.FRONTEND.zip(),
reflex/utils/console.py CHANGED
@@ -15,6 +15,8 @@ from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
15
15
  from rich.prompt import Prompt
16
16
 
17
17
  from reflex.constants import LogLevel
18
+ from reflex.constants.base import Reflex
19
+ from reflex.utils.decorator import once
18
20
 
19
21
  # Console for pretty printing.
20
22
  _console = Console()
@@ -59,9 +61,8 @@ def set_log_level(log_level: LogLevel | None):
59
61
  if log_level is None:
60
62
  return
61
63
  if not isinstance(log_level, LogLevel):
62
- raise TypeError(
63
- f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead."
64
- )
64
+ msg = f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead."
65
+ raise TypeError(msg)
65
66
  global _LOG_LEVEL
66
67
  if log_level != _LOG_LEVEL:
67
68
  # Set the loglevel persistenly for subprocesses.
@@ -89,11 +90,55 @@ def print(msg: str, dedupe: bool = False, **kwargs):
89
90
  if dedupe:
90
91
  if msg in _EMITTED_PRINTS:
91
92
  return
92
- else:
93
- _EMITTED_PRINTS.add(msg)
93
+ _EMITTED_PRINTS.add(msg)
94
94
  _console.print(msg, **kwargs)
95
95
 
96
96
 
97
+ @once
98
+ def log_file_console():
99
+ """Create a console that logs to a file.
100
+
101
+ Returns:
102
+ A Console object that logs to a file.
103
+ """
104
+ from reflex.environment import environment
105
+
106
+ if not (env_log_file := environment.REFLEX_LOG_FILE.get()):
107
+ subseconds = int((time.time() % 1) * 1000)
108
+ timestamp = time.strftime("%Y-%m-%d_%H-%M-%S") + f"_{subseconds:03d}"
109
+ log_file = Reflex.DIR / "logs" / (timestamp + ".log")
110
+ log_file.parent.mkdir(parents=True, exist_ok=True)
111
+ else:
112
+ log_file = env_log_file
113
+ if log_file.exists():
114
+ log_file.unlink()
115
+ log_file.touch()
116
+ return Console(file=log_file.open("a", encoding="utf-8"))
117
+
118
+
119
+ @once
120
+ def should_use_log_file_console() -> bool:
121
+ """Check if the log file console should be used.
122
+
123
+ Returns:
124
+ True if the log file console should be used, False otherwise.
125
+ """
126
+ from reflex.environment import environment
127
+
128
+ return environment.REFLEX_ENABLE_FULL_LOGGING.get()
129
+
130
+
131
+ def print_to_log_file(msg: str, dedupe: bool = False, **kwargs):
132
+ """Print a message to the log file.
133
+
134
+ Args:
135
+ msg: The message to print.
136
+ dedupe: If True, suppress multiple console logs of print message.
137
+ kwargs: Keyword arguments to pass to the print function.
138
+ """
139
+ log_file_console().print(msg, **kwargs)
140
+
141
+
97
142
  def debug(msg: str, dedupe: bool = False, **kwargs):
98
143
  """Print a debug message.
99
144
 
@@ -107,12 +152,13 @@ def debug(msg: str, dedupe: bool = False, **kwargs):
107
152
  if dedupe:
108
153
  if msg_ in _EMITTED_DEBUG:
109
154
  return
110
- else:
111
- _EMITTED_DEBUG.add(msg_)
155
+ _EMITTED_DEBUG.add(msg_)
112
156
  if progress := kwargs.pop("progress", None):
113
157
  progress.console.print(msg_, **kwargs)
114
158
  else:
115
159
  print(msg_, **kwargs)
160
+ if should_use_log_file_console() and kwargs.pop("progress", None) is None:
161
+ print_to_log_file(f"[purple]Debug: {msg}[/purple]", **kwargs)
116
162
 
117
163
 
118
164
  def info(msg: str, dedupe: bool = False, **kwargs):
@@ -127,9 +173,10 @@ def info(msg: str, dedupe: bool = False, **kwargs):
127
173
  if dedupe:
128
174
  if msg in _EMITTED_INFO:
129
175
  return
130
- else:
131
- _EMITTED_INFO.add(msg)
176
+ _EMITTED_INFO.add(msg)
132
177
  print(f"[cyan]Info: {msg}[/cyan]", **kwargs)
178
+ if should_use_log_file_console():
179
+ print_to_log_file(f"[cyan]Info: {msg}[/cyan]", **kwargs)
133
180
 
134
181
 
135
182
  def success(msg: str, dedupe: bool = False, **kwargs):
@@ -144,9 +191,10 @@ def success(msg: str, dedupe: bool = False, **kwargs):
144
191
  if dedupe:
145
192
  if msg in _EMITTED_SUCCESS:
146
193
  return
147
- else:
148
- _EMITTED_SUCCESS.add(msg)
194
+ _EMITTED_SUCCESS.add(msg)
149
195
  print(f"[green]Success: {msg}[/green]", **kwargs)
196
+ if should_use_log_file_console():
197
+ print_to_log_file(f"[green]Success: {msg}[/green]", **kwargs)
150
198
 
151
199
 
152
200
  def log(msg: str, dedupe: bool = False, **kwargs):
@@ -161,9 +209,10 @@ def log(msg: str, dedupe: bool = False, **kwargs):
161
209
  if dedupe:
162
210
  if msg in _EMITTED_LOGS:
163
211
  return
164
- else:
165
- _EMITTED_LOGS.add(msg)
212
+ _EMITTED_LOGS.add(msg)
166
213
  _console.log(msg, **kwargs)
214
+ if should_use_log_file_console():
215
+ print_to_log_file(msg, **kwargs)
167
216
 
168
217
 
169
218
  def rule(title: str, **kwargs):
@@ -188,9 +237,10 @@ def warn(msg: str, dedupe: bool = False, **kwargs):
188
237
  if dedupe:
189
238
  if msg in _EMIITED_WARNINGS:
190
239
  return
191
- else:
192
- _EMIITED_WARNINGS.add(msg)
240
+ _EMIITED_WARNINGS.add(msg)
193
241
  print(f"[orange1]Warning: {msg}[/orange1]", **kwargs)
242
+ if should_use_log_file_console():
243
+ print_to_log_file(f"[orange1]Warning: {msg}[/orange1]", **kwargs)
194
244
 
195
245
 
196
246
  def _get_first_non_framework_frame() -> FrameType | None:
@@ -255,6 +305,8 @@ def deprecate(
255
305
  )
256
306
  if _LOG_LEVEL <= LogLevel.WARNING:
257
307
  print(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs)
308
+ if should_use_log_file_console():
309
+ print_to_log_file(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs)
258
310
  if dedupe:
259
311
  _EMITTED_DEPRECATION_WARNINGS.add(dedupe_key)
260
312
 
@@ -271,9 +323,10 @@ def error(msg: str, dedupe: bool = False, **kwargs):
271
323
  if dedupe:
272
324
  if msg in _EMITTED_ERRORS:
273
325
  return
274
- else:
275
- _EMITTED_ERRORS.add(msg)
326
+ _EMITTED_ERRORS.add(msg)
276
327
  print(f"[red]{msg}[/red]", **kwargs)
328
+ if should_use_log_file_console():
329
+ print_to_log_file(f"[red]{msg}[/red]", **kwargs)
277
330
 
278
331
 
279
332
  def ask(
reflex/utils/exec.py CHANGED
@@ -12,13 +12,15 @@ import subprocess
12
12
  import sys
13
13
  from collections.abc import Sequence
14
14
  from pathlib import Path
15
+ from typing import NamedTuple, TypedDict
15
16
  from urllib.parse import urljoin
16
17
 
17
18
  import psutil
18
19
 
19
20
  from reflex import constants
20
- from reflex.config import environment, get_config
21
+ from reflex.config import get_config
21
22
  from reflex.constants.base import LogLevel
23
+ from reflex.environment import environment
22
24
  from reflex.utils import console, path_ops
23
25
  from reflex.utils.decorator import once
24
26
  from reflex.utils.prerequisites import get_web_dir
@@ -27,26 +29,102 @@ from reflex.utils.prerequisites import get_web_dir
27
29
  frontend_process = None
28
30
 
29
31
 
30
- def detect_package_change(json_file_path: Path) -> str:
31
- """Calculates the SHA-256 hash of a JSON file and returns it as a hexadecimal string.
32
+ def get_package_json_and_hash(package_json_path: Path) -> tuple[PackageJson, str]:
33
+ """Get the content of package.json and its hash.
32
34
 
33
35
  Args:
34
- json_file_path: The path to the JSON file to be hashed.
36
+ package_json_path: The path to the package.json file.
35
37
 
36
38
  Returns:
37
- str: The SHA-256 hash of the JSON file as a hexadecimal string.
38
-
39
- Example:
40
- >>> detect_package_change("package.json")
41
- 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2'
39
+ A tuple containing the content of package.json as a dictionary and its SHA-256 hash.
42
40
  """
43
- with json_file_path.open("r") as file:
41
+ with package_json_path.open("r") as file:
44
42
  json_data = json.load(file)
45
43
 
46
44
  # Calculate the hash
47
45
  json_string = json.dumps(json_data, sort_keys=True)
48
46
  hash_object = hashlib.sha256(json_string.encode())
49
- return hash_object.hexdigest()
47
+ return (json_data, hash_object.hexdigest())
48
+
49
+
50
+ class PackageJson(TypedDict):
51
+ """package.json content."""
52
+
53
+ dependencies: dict[str, str]
54
+ devDependencies: dict[str, str]
55
+
56
+
57
+ class Change(NamedTuple):
58
+ """A named tuple to represent a change in package dependencies."""
59
+
60
+ added: set[str]
61
+ removed: set[str]
62
+
63
+
64
+ def format_change(name: str, change: Change) -> str:
65
+ """Format the change for display.
66
+
67
+ Args:
68
+ name: The name of the change (e.g., "dependencies" or "devDependencies").
69
+ change: The Change named tuple containing added and removed packages.
70
+
71
+ Returns:
72
+ A formatted string representing the changes.
73
+ """
74
+ if not change.added and not change.removed:
75
+ return ""
76
+ added_str = ", ".join(sorted(change.added))
77
+ removed_str = ", ".join(sorted(change.removed))
78
+ change_str = f"{name}:\n"
79
+ if change.added:
80
+ change_str += f" Added: {added_str}\n"
81
+ if change.removed:
82
+ change_str += f" Removed: {removed_str}\n"
83
+ return change_str.strip()
84
+
85
+
86
+ def get_different_packages(
87
+ old_package_json_content: PackageJson,
88
+ new_package_json_content: PackageJson,
89
+ ) -> tuple[Change, Change]:
90
+ """Get the packages that are different between two package JSON contents.
91
+
92
+ Args:
93
+ old_package_json_content: The content of the old package JSON.
94
+ new_package_json_content: The content of the new package JSON.
95
+
96
+ Returns:
97
+ A tuple containing two `Change` named tuples:
98
+ - The first `Change` contains the changes in the `dependencies` section.
99
+ - The second `Change` contains the changes in the `devDependencies` section.
100
+ """
101
+
102
+ def get_changes(old: dict[str, str], new: dict[str, str]) -> Change:
103
+ """Get the changes between two dictionaries.
104
+
105
+ Args:
106
+ old: The old dictionary of packages.
107
+ new: The new dictionary of packages.
108
+
109
+ Returns:
110
+ A `Change` named tuple containing the added and removed packages.
111
+ """
112
+ old_keys = set(old.keys())
113
+ new_keys = set(new.keys())
114
+ added = new_keys - old_keys
115
+ removed = old_keys - new_keys
116
+ return Change(added=added, removed=removed)
117
+
118
+ dependencies_change = get_changes(
119
+ old_package_json_content.get("dependencies", {}),
120
+ new_package_json_content.get("dependencies", {}),
121
+ )
122
+ dev_dependencies_change = get_changes(
123
+ old_package_json_content.get("devDependencies", {}),
124
+ new_package_json_content.get("devDependencies", {}),
125
+ )
126
+
127
+ return dependencies_change, dev_dependencies_change
50
128
 
51
129
 
52
130
  def kill(proc_pid: int):
@@ -86,7 +164,7 @@ def run_process_and_launch_url(
86
164
  from reflex.utils import processes
87
165
 
88
166
  json_file_path = get_web_dir() / constants.PackageJson.PATH
89
- last_hash = detect_package_change(json_file_path)
167
+ last_content, last_hash = get_package_json_and_hash(json_file_path)
90
168
  process = None
91
169
  first_run = True
92
170
 
@@ -105,6 +183,18 @@ def run_process_and_launch_url(
105
183
  frontend_process = process
106
184
  if process.stdout:
107
185
  for line in processes.stream_logs("Starting frontend", process):
186
+ new_content, new_hash = get_package_json_and_hash(json_file_path)
187
+ if new_hash != last_hash:
188
+ dependencies_change, dev_dependencies_change = (
189
+ get_different_packages(last_content, new_content)
190
+ )
191
+ last_content, last_hash = new_content, new_hash
192
+ console.info(
193
+ "Detected changes in package.json.\n"
194
+ + format_change("Dependencies", dependencies_change)
195
+ + format_change("Dev Dependencies", dev_dependencies_change)
196
+ )
197
+
108
198
  match = re.search(constants.Next.FRONTEND_LISTENING_REGEX, line)
109
199
  if match:
110
200
  if first_run:
@@ -119,22 +209,8 @@ def run_process_and_launch_url(
119
209
  notify_backend()
120
210
  first_run = False
121
211
  else:
122
- console.print("New packages detected: Updating app...")
123
- else:
124
- if any(
125
- x in line for x in ("bin executable does not exist on disk",)
126
- ):
127
- console.error(
128
- "Try setting `REFLEX_USE_NPM=1` and re-running `reflex init` and `reflex run` to use npm instead of bun:\n"
129
- "`REFLEX_USE_NPM=1 reflex init`\n"
130
- "`REFLEX_USE_NPM=1 reflex run`"
131
- )
132
- new_hash = detect_package_change(json_file_path)
133
- if new_hash != last_hash:
134
- last_hash = new_hash
135
- kill(process.pid)
136
- process = None
137
- break # for line in process.stdout
212
+ console.print("Frontend is restarting...")
213
+
138
214
  if process is not None:
139
215
  break # while True
140
216
 
@@ -244,14 +320,12 @@ def get_app_file() -> Path:
244
320
  sys.path.insert(0, current_working_dir)
245
321
  module_spec = importlib.util.find_spec(get_app_module())
246
322
  if module_spec is None:
247
- raise ImportError(
248
- f"Module {get_app_module()} not found. Make sure the module is installed."
249
- )
323
+ msg = f"Module {get_app_module()} not found. Make sure the module is installed."
324
+ raise ImportError(msg)
250
325
  file_name = module_spec.origin
251
326
  if file_name is None:
252
- raise ImportError(
253
- f"Module {get_app_module()} not found. Make sure the module is installed."
254
- )
327
+ msg = f"Module {get_app_module()} not found. Make sure the module is installed."
328
+ raise ImportError(msg)
255
329
  return Path(file_name).resolve()
256
330
 
257
331
 
@@ -300,13 +374,12 @@ def get_reload_paths() -> Sequence[Path]:
300
374
  The reload paths for the backend.
301
375
  """
302
376
  config = get_config()
303
- reload_paths = [Path(config.app_name).parent]
304
- if config.app_module is not None and config.app_module.__file__:
305
- module_path = Path(config.app_module.__file__).resolve().parent
377
+ reload_paths = [Path.cwd()]
378
+ if (spec := importlib.util.find_spec(config.module)) is not None and spec.origin:
379
+ module_path = Path(spec.origin).resolve().parent
306
380
 
307
381
  while module_path.parent.name and any(
308
- sibling_file.name == "__init__.py"
309
- for sibling_file in module_path.parent.iterdir()
382
+ sibling_file.name == "__init__.py" for sibling_file in module_path.iterdir()
310
383
  ):
311
384
  # go up a level to find dir without `__init__.py`
312
385
  module_path = module_path.parent
@@ -383,6 +456,7 @@ HOTRELOAD_IGNORE_EXTENSIONS = (
383
456
  "json",
384
457
  "sh",
385
458
  "bash",
459
+ "log",
386
460
  )
387
461
 
388
462
  HOTRELOAD_IGNORE_PATTERNS = (
reflex/utils/export.py CHANGED
@@ -3,7 +3,8 @@
3
3
  from pathlib import Path
4
4
 
5
5
  from reflex import constants
6
- from reflex.config import environment, get_config
6
+ from reflex.config import get_config
7
+ from reflex.environment import environment
7
8
  from reflex.utils import build, console, exec, prerequisites, telemetry
8
9
 
9
10