reflex 0.6.4a2__py3-none-any.whl → 0.6.5__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 (228) hide show
  1. reflex/.templates/jinja/web/pages/custom_component.js.jinja2 +0 -14
  2. reflex/.templates/jinja/web/pages/utils.js.jinja2 +4 -8
  3. reflex/.templates/web/components/shiki/code.js +16 -11
  4. reflex/.templates/web/utils/state.js +29 -21
  5. reflex/__init__.py +4 -0
  6. reflex/__init__.pyi +4 -0
  7. reflex/app.py +148 -154
  8. reflex/app_mixins/lifespan.py +5 -1
  9. reflex/app_mixins/middleware.py +3 -1
  10. reflex/app_mixins/mixin.py +3 -2
  11. reflex/base.py +2 -4
  12. reflex/compiler/compiler.py +111 -37
  13. reflex/components/base/app_wrap.pyi +17 -17
  14. reflex/components/base/bare.py +72 -3
  15. reflex/components/base/body.pyi +17 -17
  16. reflex/components/base/document.pyi +81 -81
  17. reflex/components/base/error_boundary.pyi +25 -18
  18. reflex/components/base/fragment.pyi +17 -17
  19. reflex/components/base/head.pyi +33 -33
  20. reflex/components/base/link.pyi +33 -33
  21. reflex/components/base/meta.pyi +65 -65
  22. reflex/components/base/script.py +4 -4
  23. reflex/components/base/script.pyi +23 -20
  24. reflex/components/component.py +250 -31
  25. reflex/components/core/banner.py +1 -1
  26. reflex/components/core/banner.pyi +81 -81
  27. reflex/components/core/client_side_routing.pyi +33 -33
  28. reflex/components/core/clipboard.py +2 -2
  29. reflex/components/core/clipboard.pyi +24 -18
  30. reflex/components/core/debounce.py +2 -2
  31. reflex/components/core/debounce.pyi +18 -18
  32. reflex/components/core/html.pyi +17 -17
  33. reflex/components/core/upload.py +90 -28
  34. reflex/components/core/upload.pyi +128 -72
  35. reflex/components/datadisplay/code.py +55 -40
  36. reflex/components/datadisplay/code.pyi +46 -44
  37. reflex/components/datadisplay/dataeditor.py +21 -20
  38. reflex/components/datadisplay/dataeditor.pyi +103 -35
  39. reflex/components/datadisplay/shiki_code_block.py +60 -27
  40. reflex/components/datadisplay/shiki_code_block.pyi +86 -65
  41. reflex/components/dynamic.py +9 -5
  42. reflex/components/el/element.pyi +17 -17
  43. reflex/components/el/elements/base.pyi +17 -17
  44. reflex/components/el/elements/forms.py +12 -3
  45. reflex/components/el/elements/forms.pyi +293 -233
  46. reflex/components/el/elements/inline.pyi +449 -449
  47. reflex/components/el/elements/media.pyi +401 -401
  48. reflex/components/el/elements/metadata.pyi +97 -97
  49. reflex/components/el/elements/other.pyi +113 -113
  50. reflex/components/el/elements/scripts.pyi +49 -49
  51. reflex/components/el/elements/sectioning.pyi +241 -241
  52. reflex/components/el/elements/tables.pyi +161 -161
  53. reflex/components/el/elements/typography.pyi +241 -241
  54. reflex/components/gridjs/datatable.pyi +33 -33
  55. reflex/components/lucide/icon.py +1 -1
  56. reflex/components/lucide/icon.pyi +33 -33
  57. reflex/components/markdown/markdown.py +180 -49
  58. reflex/components/markdown/markdown.pyi +36 -19
  59. reflex/components/moment/moment.py +17 -21
  60. reflex/components/moment/moment.pyi +26 -21
  61. reflex/components/next/base.pyi +17 -17
  62. reflex/components/next/image.py +3 -3
  63. reflex/components/next/image.pyi +21 -19
  64. reflex/components/next/link.pyi +17 -17
  65. reflex/components/next/video.pyi +17 -17
  66. reflex/components/plotly/plotly.py +79 -78
  67. reflex/components/plotly/plotly.pyi +91 -41
  68. reflex/components/props.py +34 -0
  69. reflex/components/radix/primitives/accordion.py +15 -8
  70. reflex/components/radix/primitives/accordion.pyi +121 -118
  71. reflex/components/radix/primitives/base.pyi +33 -33
  72. reflex/components/radix/primitives/drawer.py +41 -20
  73. reflex/components/radix/primitives/drawer.pyi +279 -190
  74. reflex/components/radix/primitives/form.py +2 -2
  75. reflex/components/radix/primitives/form.pyi +200 -167
  76. reflex/components/radix/primitives/progress.pyi +81 -81
  77. reflex/components/radix/primitives/slider.pyi +89 -83
  78. reflex/components/radix/themes/base.py +30 -1
  79. reflex/components/radix/themes/base.pyi +286 -113
  80. reflex/components/radix/themes/color_mode.py +17 -9
  81. reflex/components/radix/themes/color_mode.pyi +68 -56
  82. reflex/components/radix/themes/components/alert_dialog.py +8 -5
  83. reflex/components/radix/themes/components/alert_dialog.pyi +125 -117
  84. reflex/components/radix/themes/components/aspect_ratio.pyi +17 -17
  85. reflex/components/radix/themes/components/avatar.py +1 -5
  86. reflex/components/radix/themes/components/avatar.pyi +17 -17
  87. reflex/components/radix/themes/components/badge.py +1 -5
  88. reflex/components/radix/themes/components/badge.pyi +17 -17
  89. reflex/components/radix/themes/components/button.pyi +18 -21
  90. reflex/components/radix/themes/components/callout.py +1 -4
  91. reflex/components/radix/themes/components/callout.pyi +81 -81
  92. reflex/components/radix/themes/components/card.py +1 -3
  93. reflex/components/radix/themes/components/card.pyi +17 -17
  94. reflex/components/radix/themes/components/checkbox.py +4 -8
  95. reflex/components/radix/themes/components/checkbox.pyi +61 -52
  96. reflex/components/radix/themes/components/checkbox_cards.pyi +33 -33
  97. reflex/components/radix/themes/components/checkbox_group.pyi +33 -33
  98. reflex/components/radix/themes/components/context_menu.py +121 -28
  99. reflex/components/radix/themes/components/context_menu.pyi +250 -147
  100. reflex/components/radix/themes/components/data_list.pyi +65 -65
  101. reflex/components/radix/themes/components/dialog.py +11 -11
  102. reflex/components/radix/themes/components/dialog.pyi +135 -120
  103. reflex/components/radix/themes/components/dropdown_menu.py +14 -25
  104. reflex/components/radix/themes/components/dropdown_menu.pyi +157 -145
  105. reflex/components/radix/themes/components/hover_card.py +19 -7
  106. reflex/components/radix/themes/components/hover_card.pyi +102 -67
  107. reflex/components/radix/themes/components/icon_button.pyi +18 -21
  108. reflex/components/radix/themes/components/inset.py +1 -3
  109. reflex/components/radix/themes/components/inset.pyi +17 -17
  110. reflex/components/radix/themes/components/popover.py +22 -13
  111. reflex/components/radix/themes/components/popover.pyi +98 -72
  112. reflex/components/radix/themes/components/progress.pyi +17 -17
  113. reflex/components/radix/themes/components/radio.pyi +17 -17
  114. reflex/components/radix/themes/components/radio_cards.py +2 -2
  115. reflex/components/radix/themes/components/radio_cards.pyi +37 -34
  116. reflex/components/radix/themes/components/radio_group.py +3 -7
  117. reflex/components/radix/themes/components/radio_group.pyi +69 -66
  118. reflex/components/radix/themes/components/scroll_area.py +1 -3
  119. reflex/components/radix/themes/components/scroll_area.pyi +17 -17
  120. reflex/components/radix/themes/components/segmented_control.pyi +37 -34
  121. reflex/components/radix/themes/components/select.py +7 -11
  122. reflex/components/radix/themes/components/select.pyi +175 -154
  123. reflex/components/radix/themes/components/separator.py +1 -4
  124. reflex/components/radix/themes/components/separator.pyi +17 -17
  125. reflex/components/radix/themes/components/skeleton.pyi +17 -17
  126. reflex/components/radix/themes/components/slider.py +12 -21
  127. reflex/components/radix/themes/components/slider.pyi +47 -25
  128. reflex/components/radix/themes/components/spinner.py +1 -4
  129. reflex/components/radix/themes/components/spinner.pyi +17 -17
  130. reflex/components/radix/themes/components/switch.py +3 -6
  131. reflex/components/radix/themes/components/switch.pyi +21 -18
  132. reflex/components/radix/themes/components/table.py +21 -5
  133. reflex/components/radix/themes/components/table.pyi +392 -116
  134. reflex/components/radix/themes/components/tabs.py +3 -6
  135. reflex/components/radix/themes/components/tabs.pyi +89 -83
  136. reflex/components/radix/themes/components/text_area.py +1 -5
  137. reflex/components/radix/themes/components/text_area.pyi +43 -20
  138. reflex/components/radix/themes/components/text_field.py +1 -5
  139. reflex/components/radix/themes/components/text_field.pyi +101 -55
  140. reflex/components/radix/themes/components/tooltip.py +5 -7
  141. reflex/components/radix/themes/components/tooltip.pyi +25 -22
  142. reflex/components/radix/themes/layout/base.py +2 -27
  143. reflex/components/radix/themes/layout/base.pyi +82 -82
  144. reflex/components/radix/themes/layout/box.pyi +17 -17
  145. reflex/components/radix/themes/layout/center.pyi +17 -17
  146. reflex/components/radix/themes/layout/container.pyi +17 -17
  147. reflex/components/radix/themes/layout/flex.py +1 -6
  148. reflex/components/radix/themes/layout/flex.pyi +17 -17
  149. reflex/components/radix/themes/layout/grid.py +1 -6
  150. reflex/components/radix/themes/layout/grid.pyi +17 -17
  151. reflex/components/radix/themes/layout/list.py +20 -15
  152. reflex/components/radix/themes/layout/list.pyi +175 -92
  153. reflex/components/radix/themes/layout/section.pyi +17 -17
  154. reflex/components/radix/themes/layout/spacer.pyi +17 -17
  155. reflex/components/radix/themes/layout/stack.py +6 -6
  156. reflex/components/radix/themes/layout/stack.pyi +91 -62
  157. reflex/components/radix/themes/typography/blockquote.py +2 -8
  158. reflex/components/radix/themes/typography/blockquote.pyi +17 -17
  159. reflex/components/radix/themes/typography/code.py +4 -10
  160. reflex/components/radix/themes/typography/code.pyi +19 -18
  161. reflex/components/radix/themes/typography/heading.py +4 -11
  162. reflex/components/radix/themes/typography/heading.pyi +19 -18
  163. reflex/components/radix/themes/typography/link.py +4 -10
  164. reflex/components/radix/themes/typography/link.pyi +19 -18
  165. reflex/components/radix/themes/typography/text.py +4 -11
  166. reflex/components/radix/themes/typography/text.pyi +115 -114
  167. reflex/components/react_player/audio.pyi +58 -33
  168. reflex/components/react_player/react_player.py +17 -17
  169. reflex/components/react_player/react_player.pyi +55 -33
  170. reflex/components/react_player/video.pyi +58 -33
  171. reflex/components/recharts/cartesian.py +45 -45
  172. reflex/components/recharts/cartesian.pyi +389 -304
  173. reflex/components/recharts/charts.py +22 -22
  174. reflex/components/recharts/charts.pyi +226 -179
  175. reflex/components/recharts/general.py +26 -27
  176. reflex/components/recharts/general.pyi +106 -99
  177. reflex/components/recharts/polar.py +33 -33
  178. reflex/components/recharts/polar.pyi +70 -64
  179. reflex/components/recharts/recharts.pyi +33 -33
  180. reflex/components/sonner/toast.py +9 -36
  181. reflex/components/sonner/toast.pyi +20 -24
  182. reflex/components/suneditor/editor.py +8 -8
  183. reflex/components/suneditor/editor.pyi +50 -25
  184. reflex/components/tags/iter_tag.py +1 -10
  185. reflex/components/tags/tag.py +1 -4
  186. reflex/config.py +252 -41
  187. reflex/constants/__init__.py +4 -16
  188. reflex/constants/base.py +7 -14
  189. reflex/constants/colors.py +0 -1
  190. reflex/constants/installer.py +12 -7
  191. reflex/constants/state.py +4 -0
  192. reflex/custom_components/custom_components.py +6 -6
  193. reflex/event.py +486 -241
  194. reflex/experimental/client_state.py +9 -9
  195. reflex/experimental/layout.py +2 -2
  196. reflex/experimental/layout.pyi +95 -87
  197. reflex/experimental/misc.py +1 -1
  198. reflex/istate/__init__.py +1 -0
  199. reflex/istate/proxy.py +33 -0
  200. reflex/istate/wrappers.py +27 -0
  201. reflex/model.py +7 -7
  202. reflex/page.py +2 -1
  203. reflex/reflex.py +142 -8
  204. reflex/state.py +133 -46
  205. reflex/testing.py +9 -7
  206. reflex/utils/console.py +0 -1
  207. reflex/utils/exceptions.py +31 -3
  208. reflex/utils/exec.py +33 -14
  209. reflex/utils/format.py +15 -12
  210. reflex/utils/net.py +1 -1
  211. reflex/utils/path_ops.py +2 -2
  212. reflex/utils/prerequisites.py +82 -46
  213. reflex/utils/pyi_generator.py +63 -20
  214. reflex/utils/registry.py +1 -1
  215. reflex/utils/serializers.py +75 -36
  216. reflex/utils/telemetry.py +3 -2
  217. reflex/utils/types.py +125 -10
  218. reflex/vars/base.py +131 -119
  219. reflex/vars/function.py +59 -12
  220. reflex/vars/number.py +3 -1
  221. reflex/vars/object.py +30 -24
  222. reflex/vars/sequence.py +7 -7
  223. {reflex-0.6.4a2.dist-info → reflex-0.6.5.dist-info}/METADATA +3 -3
  224. reflex-0.6.5.dist-info/RECORD +394 -0
  225. reflex-0.6.4a2.dist-info/RECORD +0 -391
  226. {reflex-0.6.4a2.dist-info → reflex-0.6.5.dist-info}/LICENSE +0 -0
  227. {reflex-0.6.4a2.dist-info → reflex-0.6.5.dist-info}/WHEEL +0 -0
  228. {reflex-0.6.4a2.dist-info → reflex-0.6.5.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,7 @@
1
1
  """Custom Exceptions."""
2
2
 
3
+ from typing import NoReturn
4
+
3
5
 
4
6
  class ReflexError(Exception):
5
7
  """Base exception for all Reflex exceptions."""
@@ -89,12 +91,12 @@ class MatchTypeError(ReflexError, TypeError):
89
91
  """Raised when the return types of match cases are different."""
90
92
 
91
93
 
92
- class EventHandlerArgMismatch(ReflexError, TypeError):
93
- """Raised when the number of args accepted by an EventHandler is differs from that provided by the event trigger."""
94
+ class EventHandlerArgTypeMismatch(ReflexError, TypeError):
95
+ """Raised when the annotations of args accepted by an EventHandler differs from the spec of the event trigger."""
94
96
 
95
97
 
96
98
  class EventFnArgMismatch(ReflexError, TypeError):
97
- """Raised when the number of args accepted by a lambda differs from that provided by the event trigger."""
99
+ """Raised when the number of args required by an event handler is more than provided by the event trigger."""
98
100
 
99
101
 
100
102
  class DynamicRouteArgShadowsStateVar(ReflexError, NameError):
@@ -143,3 +145,29 @@ class EnvironmentVarValueError(ReflexError, ValueError):
143
145
 
144
146
  class DynamicComponentInvalidSignature(ReflexError, TypeError):
145
147
  """Raised when a dynamic component has an invalid signature."""
148
+
149
+
150
+ class InvalidPropValueError(ReflexError):
151
+ """Raised when a prop value is invalid."""
152
+
153
+
154
+ class SystemPackageMissingError(ReflexError):
155
+ """Raised when a system package is missing."""
156
+
157
+
158
+ def raise_system_package_missing_error(package: str) -> NoReturn:
159
+ """Raise a SystemPackageMissingError.
160
+
161
+ Args:
162
+ package: The name of the missing system package.
163
+
164
+ Raises:
165
+ SystemPackageMissingError: The raised exception.
166
+ """
167
+ from reflex.constants import IS_MACOS
168
+
169
+ raise SystemPackageMissingError(
170
+ f"System package '{package}' is missing."
171
+ " Please install it through your system package manager."
172
+ + (f" You can do so by running 'brew install {package}'." if IS_MACOS else "")
173
+ )
reflex/utils/exec.py CHANGED
@@ -184,7 +184,7 @@ def should_use_granian():
184
184
  Returns:
185
185
  True if Granian should be used.
186
186
  """
187
- return environment.REFLEX_USE_GRANIAN
187
+ return environment.REFLEX_USE_GRANIAN.get()
188
188
 
189
189
 
190
190
  def get_app_module():
@@ -369,7 +369,9 @@ def run_uvicorn_backend_prod(host, port, loglevel):
369
369
  command,
370
370
  run=True,
371
371
  show_logs=True,
372
- env={constants.SKIP_COMPILE_ENV_VAR: "yes"}, # skip compile for prod backend
372
+ env={
373
+ environment.REFLEX_SKIP_COMPILE.name: "true"
374
+ }, # skip compile for prod backend
373
375
  )
374
376
 
375
377
 
@@ -405,7 +407,7 @@ def run_granian_backend_prod(host, port, loglevel):
405
407
  run=True,
406
408
  show_logs=True,
407
409
  env={
408
- constants.SKIP_COMPILE_ENV_VAR: "yes"
410
+ environment.REFLEX_SKIP_COMPILE.name: "true"
409
411
  }, # skip compile for prod backend
410
412
  )
411
413
  except ImportError:
@@ -427,7 +429,7 @@ def output_system_info():
427
429
  except Exception:
428
430
  config_file = None
429
431
 
430
- console.rule(f"System Info")
432
+ console.rule("System Info")
431
433
  console.debug(f"Config file: {config_file!r}")
432
434
  console.debug(f"Config: {config}")
433
435
 
@@ -467,9 +469,11 @@ def output_system_info():
467
469
  console.debug(f"{dep}")
468
470
 
469
471
  console.debug(
470
- f"Using package installer at: {prerequisites.get_install_package_manager()}" # type: ignore
472
+ f"Using package installer at: {prerequisites.get_install_package_manager(on_failure_return_none=True)}" # type: ignore
471
473
  )
472
- console.debug(f"Using package executer at: {prerequisites.get_package_manager()}") # type: ignore
474
+ console.debug(
475
+ f"Using package executer at: {prerequisites.get_package_manager(on_failure_return_none=True)}"
476
+ ) # type: ignore
473
477
  if system != "Windows":
474
478
  console.debug(f"Unzip path: {path_ops.which('unzip')}")
475
479
 
@@ -489,11 +493,8 @@ def is_prod_mode() -> bool:
489
493
  Returns:
490
494
  True if the app is running in production mode or False if running in dev mode.
491
495
  """
492
- current_mode = os.environ.get(
493
- constants.ENV_MODE_ENV_VAR,
494
- constants.Env.DEV.value,
495
- )
496
- return current_mode == constants.Env.PROD.value
496
+ current_mode = environment.REFLEX_ENV_MODE.get()
497
+ return current_mode == constants.Env.PROD
497
498
 
498
499
 
499
500
  def is_frontend_only() -> bool:
@@ -502,7 +503,13 @@ def is_frontend_only() -> bool:
502
503
  Returns:
503
504
  True if the app is running in frontend-only mode.
504
505
  """
505
- return os.environ.get(constants.ENV_FRONTEND_ONLY_ENV_VAR, "").lower() == "true"
506
+ console.deprecate(
507
+ "is_frontend_only() is deprecated and will be removed in a future release.",
508
+ reason="Use `environment.REFLEX_FRONTEND_ONLY.get()` instead.",
509
+ deprecation_version="0.6.5",
510
+ removal_version="0.7.0",
511
+ )
512
+ return environment.REFLEX_FRONTEND_ONLY.get()
506
513
 
507
514
 
508
515
  def is_backend_only() -> bool:
@@ -511,7 +518,13 @@ def is_backend_only() -> bool:
511
518
  Returns:
512
519
  True if the app is running in backend-only mode.
513
520
  """
514
- return os.environ.get(constants.ENV_BACKEND_ONLY_ENV_VAR, "").lower() == "true"
521
+ console.deprecate(
522
+ "is_backend_only() is deprecated and will be removed in a future release.",
523
+ reason="Use `environment.REFLEX_BACKEND_ONLY.get()` instead.",
524
+ deprecation_version="0.6.5",
525
+ removal_version="0.7.0",
526
+ )
527
+ return environment.REFLEX_BACKEND_ONLY.get()
515
528
 
516
529
 
517
530
  def should_skip_compile() -> bool:
@@ -520,4 +533,10 @@ def should_skip_compile() -> bool:
520
533
  Returns:
521
534
  True if the app should skip compile.
522
535
  """
523
- return os.environ.get(constants.SKIP_COMPILE_ENV_VAR) == "yes"
536
+ console.deprecate(
537
+ "should_skip_compile() is deprecated and will be removed in a future release.",
538
+ reason="Use `environment.REFLEX_SKIP_COMPILE.get()` instead.",
539
+ deprecation_version="0.6.5",
540
+ removal_version="0.7.0",
541
+ )
542
+ return environment.REFLEX_SKIP_COMPILE.get()
reflex/utils/format.py CHANGED
@@ -6,15 +6,16 @@ import inspect
6
6
  import json
7
7
  import os
8
8
  import re
9
- from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
9
+ from typing import TYPE_CHECKING, Any, List, Optional, Union
10
10
 
11
11
  from reflex import constants
12
+ from reflex.constants.state import FRONTEND_EVENT_STATE
12
13
  from reflex.utils import exceptions
13
14
  from reflex.utils.console import deprecate
14
15
 
15
16
  if TYPE_CHECKING:
16
17
  from reflex.components.component import ComponentStyle
17
- from reflex.event import ArgsSpec, EventChain, EventHandler, EventSpec
18
+ from reflex.event import ArgsSpec, EventChain, EventHandler, EventSpec, EventType
18
19
 
19
20
  WRAP_MAP = {
20
21
  "{": "}",
@@ -197,8 +198,16 @@ def make_default_page_title(app_name: str, route: str) -> str:
197
198
  Returns:
198
199
  The default page title.
199
200
  """
200
- title = constants.DefaultPage.TITLE.format(app_name, route)
201
- return to_title_case(title, " ")
201
+ route_parts = [
202
+ part
203
+ for part in route.split("/")
204
+ if part and not (part.startswith("[") and part.endswith("]"))
205
+ ]
206
+
207
+ title = constants.DefaultPage.TITLE.format(
208
+ app_name, route_parts[-1] if route_parts else constants.PageNames.INDEX_ROUTE
209
+ )
210
+ return to_title_case(title)
202
211
 
203
212
 
204
213
  def _escape_js_string(string: str) -> str:
@@ -431,7 +440,7 @@ def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]:
431
440
 
432
441
  from reflex.state import State
433
442
 
434
- if state_full_name == "state" and name not in State.__dict__:
443
+ if state_full_name == FRONTEND_EVENT_STATE and name not in State.__dict__:
435
444
  return ("", to_snake_case(handler.fn.__qualname__))
436
445
 
437
446
  return (state_full_name, name)
@@ -525,13 +534,7 @@ def format_event_chain(
525
534
 
526
535
 
527
536
  def format_queue_events(
528
- events: (
529
- EventSpec
530
- | EventHandler
531
- | Callable
532
- | List[EventSpec | EventHandler | Callable]
533
- | None
534
- ) = None,
537
+ events: EventType | None = None,
535
538
  args_spec: Optional[ArgsSpec] = None,
536
539
  ) -> Var[EventChain]:
537
540
  """Format a list of event handler / event spec as a javascript callback.
reflex/utils/net.py CHANGED
@@ -12,7 +12,7 @@ def _httpx_verify_kwarg() -> bool:
12
12
  Returns:
13
13
  True if SSL verification is enabled, False otherwise
14
14
  """
15
- return not environment.SSL_NO_VERIFY
15
+ return not environment.SSL_NO_VERIFY.get()
16
16
 
17
17
 
18
18
  def get(url: str, **kwargs) -> httpx.Response:
reflex/utils/path_ops.py CHANGED
@@ -136,7 +136,7 @@ def use_system_node() -> bool:
136
136
  Returns:
137
137
  Whether the system node should be used.
138
138
  """
139
- return environment.REFLEX_USE_SYSTEM_NODE
139
+ return environment.REFLEX_USE_SYSTEM_NODE.get()
140
140
 
141
141
 
142
142
  def use_system_bun() -> bool:
@@ -145,7 +145,7 @@ def use_system_bun() -> bool:
145
145
  Returns:
146
146
  Whether the system bun should be used.
147
147
  """
148
- return environment.REFLEX_USE_SYSTEM_BUN
148
+ return environment.REFLEX_USE_SYSTEM_BUN.get()
149
149
 
150
150
 
151
151
  def get_node_bin_path() -> Path | None:
@@ -35,7 +35,10 @@ from reflex import constants, model
35
35
  from reflex.compiler import templates
36
36
  from reflex.config import Config, environment, get_config
37
37
  from reflex.utils import console, net, path_ops, processes
38
- from reflex.utils.exceptions import GeneratedCodeHasNoFunctionDefs
38
+ from reflex.utils.exceptions import (
39
+ GeneratedCodeHasNoFunctionDefs,
40
+ raise_system_package_missing_error,
41
+ )
39
42
  from reflex.utils.format import format_library_name
40
43
  from reflex.utils.registry import _get_npm_registry
41
44
 
@@ -69,7 +72,7 @@ def get_web_dir() -> Path:
69
72
  Returns:
70
73
  The working directory.
71
74
  """
72
- return environment.REFLEX_WEB_WORKDIR
75
+ return environment.REFLEX_WEB_WORKDIR.get()
73
76
 
74
77
 
75
78
  def _python_version_check():
@@ -204,10 +207,13 @@ def get_bun_version() -> version.Version | None:
204
207
  return None
205
208
 
206
209
 
207
- def get_install_package_manager() -> str | None:
210
+ def get_install_package_manager(on_failure_return_none: bool = False) -> str | None:
208
211
  """Get the package manager executable for installation.
209
212
  Currently, bun is used for installation only.
210
213
 
214
+ Args:
215
+ on_failure_return_none: Whether to return None on failure.
216
+
211
217
  Returns:
212
218
  The path to the package manager.
213
219
  """
@@ -217,21 +223,29 @@ def get_install_package_manager() -> str | None:
217
223
  or windows_check_onedrive_in_path()
218
224
  or windows_npm_escape_hatch()
219
225
  ):
220
- return get_package_manager()
226
+ return get_package_manager(on_failure_return_none)
221
227
  return str(get_config().bun_path)
222
228
 
223
229
 
224
- def get_package_manager() -> str | None:
230
+ def get_package_manager(on_failure_return_none: bool = False) -> str | None:
225
231
  """Get the package manager executable for running app.
226
232
  Currently on unix systems, npm is used for running the app only.
227
233
 
234
+ Args:
235
+ on_failure_return_none: Whether to return None on failure.
236
+
228
237
  Returns:
229
238
  The path to the package manager.
239
+
240
+ Raises:
241
+ FileNotFoundError: If the package manager is not found.
230
242
  """
231
243
  npm_path = path_ops.get_npm_path()
232
244
  if npm_path is not None:
233
- npm_path = str(Path(npm_path).resolve())
234
- return npm_path
245
+ return str(Path(npm_path).resolve())
246
+ if on_failure_return_none:
247
+ return None
248
+ raise FileNotFoundError("NPM not found. You may need to run `reflex init`.")
235
249
 
236
250
 
237
251
  def windows_check_onedrive_in_path() -> bool:
@@ -249,7 +263,7 @@ def windows_npm_escape_hatch() -> bool:
249
263
  Returns:
250
264
  If the user has set REFLEX_USE_NPM.
251
265
  """
252
- return environment.REFLEX_USE_NPM
266
+ return environment.REFLEX_USE_NPM.get()
253
267
 
254
268
 
255
269
  def get_app(reload: bool = False) -> ModuleType:
@@ -267,7 +281,7 @@ def get_app(reload: bool = False) -> ModuleType:
267
281
  from reflex.utils import telemetry
268
282
 
269
283
  try:
270
- os.environ[constants.RELOAD_CONFIG] = str(reload)
284
+ environment.RELOAD_CONFIG.set(reload)
271
285
  config = get_config()
272
286
  if not config.app_name:
273
287
  raise RuntimeError(
@@ -430,7 +444,7 @@ def create_config(app_name: str):
430
444
 
431
445
  def initialize_gitignore(
432
446
  gitignore_file: Path = constants.GitIgnore.FILE,
433
- files_to_ignore: set[str] = constants.GitIgnore.DEFAULTS,
447
+ files_to_ignore: set[str] | list[str] = constants.GitIgnore.DEFAULTS,
434
448
  ):
435
449
  """Initialize the template .gitignore file.
436
450
 
@@ -439,23 +453,20 @@ def initialize_gitignore(
439
453
  files_to_ignore: The files to add to the .gitignore file.
440
454
  """
441
455
  # Combine with the current ignored files.
442
- current_ignore: set[str] = set()
456
+ current_ignore: list[str] = []
443
457
  if gitignore_file.exists():
444
- current_ignore |= set(
445
- line.strip() for line in gitignore_file.read_text().splitlines()
446
- )
458
+ current_ignore = [ln.strip() for ln in gitignore_file.read_text().splitlines()]
447
459
 
448
460
  if files_to_ignore == current_ignore:
449
461
  console.debug(f"{gitignore_file} already up to date.")
450
462
  return
451
- files_to_ignore |= current_ignore
463
+ files_to_ignore = [ln for ln in files_to_ignore if ln not in current_ignore]
464
+ files_to_ignore += current_ignore
452
465
 
453
466
  # Write files to the .gitignore file.
454
467
  gitignore_file.touch(exist_ok=True)
455
468
  console.debug(f"Creating {gitignore_file}")
456
- gitignore_file.write_text(
457
- "\n".join(sorted(files_to_ignore)) + "\n",
458
- )
469
+ gitignore_file.write_text("\n".join(files_to_ignore) + "\n")
459
470
 
460
471
 
461
472
  def initialize_requirements_txt():
@@ -587,6 +598,8 @@ def initialize_web_directory():
587
598
 
588
599
  initialize_package_json()
589
600
 
601
+ initialize_bun_config()
602
+
590
603
  path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
591
604
 
592
605
  update_next_config()
@@ -611,17 +624,21 @@ def _compile_package_json():
611
624
  def initialize_package_json():
612
625
  """Render and write in .web the package.json file."""
613
626
  output_path = get_web_dir() / constants.PackageJson.PATH
614
- code = _compile_package_json()
615
- output_path.write_text(code)
627
+ output_path.write_text(_compile_package_json())
628
+
616
629
 
617
- best_registry = _get_npm_registry()
630
+ def initialize_bun_config():
631
+ """Initialize the bun config file."""
618
632
  bun_config_path = get_web_dir() / constants.Bun.CONFIG_PATH
619
- bun_config_path.write_text(
620
- f"""
621
- [install]
622
- registry = "{best_registry}"
623
- """
624
- )
633
+
634
+ if (custom_bunfig := Path(constants.Bun.CONFIG_PATH)).exists():
635
+ bunfig_content = custom_bunfig.read_text()
636
+ console.info(f"Copying custom bunfig.toml inside {get_web_dir()} folder")
637
+ else:
638
+ best_registry = _get_npm_registry()
639
+ bunfig_content = constants.Bun.DEFAULT_CONFIG.format(registry=best_registry)
640
+
641
+ bun_config_path.write_text(bunfig_content)
625
642
 
626
643
 
627
644
  def init_reflex_json(project_hash: int | None):
@@ -677,6 +694,7 @@ def _update_next_config(
677
694
  "compress": config.next_compression,
678
695
  "reactStrictMode": config.react_strict_mode,
679
696
  "trailingSlash": True,
697
+ "staticPageGenerationTimeout": config.static_page_generation_timeout,
680
698
  }
681
699
  if transpile_packages:
682
700
  next_config["transpilePackages"] = list(
@@ -811,11 +829,7 @@ def install_node():
811
829
 
812
830
 
813
831
  def install_bun():
814
- """Install bun onto the user's system.
815
-
816
- Raises:
817
- FileNotFoundError: If required packages are not found.
818
- """
832
+ """Install bun onto the user's system."""
819
833
  win_supported = is_windows_bun_supported()
820
834
  one_drive_in_path = windows_check_onedrive_in_path()
821
835
  if constants.IS_WINDOWS and not win_supported or one_drive_in_path:
@@ -854,7 +868,7 @@ def install_bun():
854
868
  else:
855
869
  unzip_path = path_ops.which("unzip")
856
870
  if unzip_path is None:
857
- raise FileNotFoundError("Reflex requires unzip to be installed.")
871
+ raise_system_package_missing_error("unzip")
858
872
 
859
873
  # Run the bun install script.
860
874
  download_and_run(
@@ -919,20 +933,39 @@ def install_frontend_packages(packages: set[str], config: Config):
919
933
  packages: A list of package names to be installed.
920
934
  config: The config object.
921
935
 
936
+ Raises:
937
+ FileNotFoundError: If the package manager is not found.
938
+
922
939
  Example:
923
940
  >>> install_frontend_packages(["react", "react-dom"], get_config())
924
941
  """
925
942
  # unsupported archs(arm and 32bit machines) will use npm anyway. so we dont have to run npm twice
926
943
  fallback_command = (
927
- get_package_manager()
928
- if not constants.IS_WINDOWS
929
- or constants.IS_WINDOWS
930
- and is_windows_bun_supported()
931
- and not windows_check_onedrive_in_path()
944
+ get_package_manager(on_failure_return_none=True)
945
+ if (
946
+ not constants.IS_WINDOWS
947
+ or constants.IS_WINDOWS
948
+ and is_windows_bun_supported()
949
+ and not windows_check_onedrive_in_path()
950
+ )
932
951
  else None
933
952
  )
953
+
954
+ install_package_manager = (
955
+ get_install_package_manager(on_failure_return_none=True) or fallback_command
956
+ )
957
+
958
+ if install_package_manager is None:
959
+ raise FileNotFoundError(
960
+ "Could not find a package manager to install frontend packages. You may need to run `reflex init`."
961
+ )
962
+
963
+ fallback_command = (
964
+ fallback_command if fallback_command is not install_package_manager else None
965
+ )
966
+
934
967
  processes.run_process_with_fallback(
935
- [get_install_package_manager(), "install"], # type: ignore
968
+ [install_package_manager, "install"], # type: ignore
936
969
  fallback=fallback_command,
937
970
  analytics_enabled=True,
938
971
  show_status_message="Installing base frontend packages",
@@ -943,7 +976,7 @@ def install_frontend_packages(packages: set[str], config: Config):
943
976
  if config.tailwind is not None:
944
977
  processes.run_process_with_fallback(
945
978
  [
946
- get_install_package_manager(),
979
+ install_package_manager,
947
980
  "add",
948
981
  "-d",
949
982
  constants.Tailwind.VERSION,
@@ -959,7 +992,7 @@ def install_frontend_packages(packages: set[str], config: Config):
959
992
  # Install custom packages defined in frontend_packages
960
993
  if len(packages) > 0:
961
994
  processes.run_process_with_fallback(
962
- [get_install_package_manager(), "add", *packages],
995
+ [install_package_manager, "add", *packages],
963
996
  fallback=fallback_command,
964
997
  analytics_enabled=True,
965
998
  show_status_message="Installing frontend packages from config and components",
@@ -991,7 +1024,7 @@ def needs_reinit(frontend: bool = True) -> bool:
991
1024
  return False
992
1025
 
993
1026
  # Make sure the .reflex directory exists.
994
- if not environment.REFLEX_DIR.exists():
1027
+ if not environment.REFLEX_DIR.get().exists():
995
1028
  return True
996
1029
 
997
1030
  # Make sure the .web directory exists in frontend mode.
@@ -1096,7 +1129,7 @@ def ensure_reflex_installation_id() -> Optional[int]:
1096
1129
  """
1097
1130
  try:
1098
1131
  initialize_reflex_user_directory()
1099
- installation_id_file = environment.REFLEX_DIR / "installation_id"
1132
+ installation_id_file = environment.REFLEX_DIR.get() / "installation_id"
1100
1133
 
1101
1134
  installation_id = None
1102
1135
  if installation_id_file.exists():
@@ -1121,7 +1154,7 @@ def ensure_reflex_installation_id() -> Optional[int]:
1121
1154
  def initialize_reflex_user_directory():
1122
1155
  """Initialize the reflex user directory."""
1123
1156
  # Create the reflex directory.
1124
- path_ops.mkdir(environment.REFLEX_DIR)
1157
+ path_ops.mkdir(environment.REFLEX_DIR.get())
1125
1158
 
1126
1159
 
1127
1160
  def initialize_frontend_dependencies():
@@ -1144,7 +1177,10 @@ def check_db_initialized() -> bool:
1144
1177
  Returns:
1145
1178
  True if alembic is initialized (or if database is not used).
1146
1179
  """
1147
- if get_config().db_url is not None and not environment.ALEMBIC_CONFIG.exists():
1180
+ if (
1181
+ get_config().db_url is not None
1182
+ and not environment.ALEMBIC_CONFIG.get().exists()
1183
+ ):
1148
1184
  console.error(
1149
1185
  "Database is not initialized. Run [bold]reflex db init[/bold] first."
1150
1186
  )
@@ -1154,7 +1190,7 @@ def check_db_initialized() -> bool:
1154
1190
 
1155
1191
  def check_schema_up_to_date():
1156
1192
  """Check if the sqlmodel metadata matches the current database schema."""
1157
- if get_config().db_url is None or not environment.ALEMBIC_CONFIG.exists():
1193
+ if get_config().db_url is None or not environment.ALEMBIC_CONFIG.get().exists():
1158
1194
  return
1159
1195
  with model.Model.get_db_engine().connect() as connection:
1160
1196
  try: