reflex 0.6.8a1__py3-none-any.whl → 0.7.0__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 (248) hide show
  1. reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +1 -1
  2. reflex/.templates/jinja/web/pages/_app.js.jinja2 +7 -7
  3. reflex/.templates/jinja/web/pages/utils.js.jinja2 +3 -3
  4. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +1 -4
  5. reflex/.templates/web/utils/state.js +65 -36
  6. reflex/__init__.py +4 -17
  7. reflex/__init__.pyi +1 -2
  8. reflex/app.py +286 -135
  9. reflex/app_mixins/lifespan.py +9 -9
  10. reflex/app_mixins/middleware.py +6 -6
  11. reflex/app_module_for_backend.py +3 -7
  12. reflex/base.py +7 -7
  13. reflex/compiler/compiler.py +8 -0
  14. reflex/compiler/utils.py +57 -18
  15. reflex/components/base/app_wrap.pyi +16 -16
  16. reflex/components/base/bare.py +1 -1
  17. reflex/components/base/body.pyi +16 -16
  18. reflex/components/base/document.pyi +76 -76
  19. reflex/components/base/error_boundary.py +2 -1
  20. reflex/components/base/error_boundary.pyi +19 -22
  21. reflex/components/base/fragment.pyi +16 -16
  22. reflex/components/base/head.pyi +31 -31
  23. reflex/components/base/link.pyi +31 -31
  24. reflex/components/base/meta.py +2 -2
  25. reflex/components/base/meta.pyi +61 -61
  26. reflex/components/base/script.pyi +19 -19
  27. reflex/components/base/strict_mode.py +10 -0
  28. reflex/components/base/strict_mode.pyi +57 -0
  29. reflex/components/component.py +38 -77
  30. reflex/components/core/banner.py +159 -4
  31. reflex/components/core/banner.pyi +162 -76
  32. reflex/components/core/breakpoints.py +3 -1
  33. reflex/components/core/client_side_routing.py +1 -1
  34. reflex/components/core/client_side_routing.pyi +32 -32
  35. reflex/components/core/clipboard.pyi +17 -20
  36. reflex/components/core/cond.py +9 -10
  37. reflex/components/core/debounce.py +1 -1
  38. reflex/components/core/debounce.pyi +17 -17
  39. reflex/components/core/foreach.py +28 -3
  40. reflex/components/core/html.py +1 -1
  41. reflex/components/core/html.pyi +16 -16
  42. reflex/components/core/match.py +5 -5
  43. reflex/components/core/sticky.py +134 -0
  44. reflex/components/core/sticky.pyi +449 -0
  45. reflex/components/core/upload.py +2 -2
  46. reflex/components/core/upload.pyi +80 -88
  47. reflex/components/datadisplay/code.py +5 -14
  48. reflex/components/datadisplay/code.pyi +31 -31
  49. reflex/components/datadisplay/dataeditor.py +7 -4
  50. reflex/components/datadisplay/dataeditor.pyi +40 -54
  51. reflex/components/datadisplay/logo.py +13 -8
  52. reflex/components/datadisplay/shiki_code_block.py +14 -9
  53. reflex/components/datadisplay/shiki_code_block.pyi +46 -46
  54. reflex/components/dynamic.py +22 -3
  55. reflex/components/el/constants/reflex.py +1 -1
  56. reflex/components/el/element.py +1 -1
  57. reflex/components/el/element.pyi +16 -16
  58. reflex/components/el/elements/base.pyi +16 -16
  59. reflex/components/el/elements/forms.py +4 -4
  60. reflex/components/el/elements/forms.pyi +224 -258
  61. reflex/components/el/elements/inline.pyi +421 -421
  62. reflex/components/el/elements/media.pyi +376 -376
  63. reflex/components/el/elements/metadata.pyi +91 -91
  64. reflex/components/el/elements/other.pyi +106 -106
  65. reflex/components/el/elements/scripts.pyi +46 -46
  66. reflex/components/el/elements/sectioning.pyi +226 -226
  67. reflex/components/el/elements/tables.pyi +151 -151
  68. reflex/components/el/elements/typography.pyi +226 -226
  69. reflex/components/gridjs/datatable.pyi +31 -31
  70. reflex/components/lucide/icon.py +46 -8
  71. reflex/components/lucide/icon.pyi +85 -31
  72. reflex/components/markdown/markdown.py +10 -8
  73. reflex/components/markdown/markdown.pyi +16 -16
  74. reflex/components/moment/moment.py +2 -2
  75. reflex/components/moment/moment.pyi +17 -19
  76. reflex/components/next/base.pyi +16 -16
  77. reflex/components/next/image.py +16 -4
  78. reflex/components/next/image.pyi +22 -20
  79. reflex/components/next/link.py +1 -1
  80. reflex/components/next/link.pyi +16 -16
  81. reflex/components/next/video.pyi +16 -16
  82. reflex/components/plotly/__init__.py +29 -2
  83. reflex/components/plotly/plotly.py +240 -5
  84. reflex/components/plotly/plotly.pyi +799 -44
  85. reflex/components/props.py +3 -3
  86. reflex/components/radix/__init__.pyi +1 -1
  87. reflex/components/radix/primitives/accordion.py +9 -5
  88. reflex/components/radix/primitives/accordion.pyi +110 -108
  89. reflex/components/radix/primitives/base.pyi +31 -31
  90. reflex/components/radix/primitives/drawer.py +5 -2
  91. reflex/components/radix/primitives/drawer.pyi +179 -187
  92. reflex/components/radix/primitives/form.pyi +160 -172
  93. reflex/components/radix/primitives/progress.py +1 -1
  94. reflex/components/radix/primitives/progress.pyi +76 -76
  95. reflex/components/radix/primitives/slider.py +1 -1
  96. reflex/components/radix/primitives/slider.pyi +78 -82
  97. reflex/components/radix/themes/base.pyi +121 -121
  98. reflex/components/radix/themes/color_mode.py +11 -9
  99. reflex/components/radix/themes/color_mode.pyi +47 -49
  100. reflex/components/radix/themes/components/alert_dialog.py +3 -0
  101. reflex/components/radix/themes/components/alert_dialog.pyi +110 -112
  102. reflex/components/radix/themes/components/aspect_ratio.pyi +16 -16
  103. reflex/components/radix/themes/components/avatar.pyi +16 -16
  104. reflex/components/radix/themes/components/badge.pyi +16 -16
  105. reflex/components/radix/themes/components/button.pyi +16 -16
  106. reflex/components/radix/themes/components/callout.pyi +76 -76
  107. reflex/components/radix/themes/components/card.py +1 -1
  108. reflex/components/radix/themes/components/card.pyi +17 -17
  109. reflex/components/radix/themes/components/checkbox.pyi +49 -55
  110. reflex/components/radix/themes/components/checkbox_cards.pyi +31 -31
  111. reflex/components/radix/themes/components/checkbox_group.pyi +31 -31
  112. reflex/components/radix/themes/components/context_menu.py +5 -0
  113. reflex/components/radix/themes/components/context_menu.pyi +149 -155
  114. reflex/components/radix/themes/components/data_list.pyi +61 -61
  115. reflex/components/radix/themes/components/dialog.py +3 -0
  116. reflex/components/radix/themes/components/dialog.pyi +113 -117
  117. reflex/components/radix/themes/components/dropdown_menu.py +5 -0
  118. reflex/components/radix/themes/components/dropdown_menu.pyi +133 -137
  119. reflex/components/radix/themes/components/hover_card.py +3 -0
  120. reflex/components/radix/themes/components/hover_card.pyi +63 -67
  121. reflex/components/radix/themes/components/icon_button.py +2 -2
  122. reflex/components/radix/themes/components/icon_button.pyi +17 -16
  123. reflex/components/radix/themes/components/inset.pyi +16 -16
  124. reflex/components/radix/themes/components/popover.py +3 -0
  125. reflex/components/radix/themes/components/popover.pyi +68 -70
  126. reflex/components/radix/themes/components/progress.pyi +16 -16
  127. reflex/components/radix/themes/components/radio.pyi +16 -16
  128. reflex/components/radix/themes/components/radio_cards.py +2 -0
  129. reflex/components/radix/themes/components/radio_cards.pyi +32 -34
  130. reflex/components/radix/themes/components/radio_group.py +1 -1
  131. reflex/components/radix/themes/components/radio_group.pyi +62 -64
  132. reflex/components/radix/themes/components/scroll_area.pyi +16 -16
  133. reflex/components/radix/themes/components/segmented_control.pyi +32 -35
  134. reflex/components/radix/themes/components/select.py +4 -0
  135. reflex/components/radix/themes/components/select.pyi +145 -157
  136. reflex/components/radix/themes/components/separator.pyi +16 -16
  137. reflex/components/radix/themes/components/skeleton.py +3 -0
  138. reflex/components/radix/themes/components/skeleton.pyi +16 -16
  139. reflex/components/radix/themes/components/slider.pyi +22 -28
  140. reflex/components/radix/themes/components/spinner.pyi +16 -16
  141. reflex/components/radix/themes/components/switch.pyi +17 -19
  142. reflex/components/radix/themes/components/table.pyi +106 -106
  143. reflex/components/radix/themes/components/tabs.py +3 -0
  144. reflex/components/radix/themes/components/tabs.pyi +78 -82
  145. reflex/components/radix/themes/components/text_area.py +12 -0
  146. reflex/components/radix/themes/components/text_area.pyi +21 -33
  147. reflex/components/radix/themes/components/text_field.py +1 -1
  148. reflex/components/radix/themes/components/text_field.pyi +52 -80
  149. reflex/components/radix/themes/components/tooltip.py +6 -1
  150. reflex/components/radix/themes/components/tooltip.pyi +20 -21
  151. reflex/components/radix/themes/layout/__init__.pyi +1 -1
  152. reflex/components/radix/themes/layout/base.pyi +16 -16
  153. reflex/components/radix/themes/layout/box.pyi +16 -16
  154. reflex/components/radix/themes/layout/center.pyi +16 -16
  155. reflex/components/radix/themes/layout/container.pyi +16 -16
  156. reflex/components/radix/themes/layout/flex.pyi +16 -16
  157. reflex/components/radix/themes/layout/grid.pyi +16 -16
  158. reflex/components/radix/themes/layout/list.py +2 -2
  159. reflex/components/radix/themes/layout/list.pyi +76 -76
  160. reflex/components/radix/themes/layout/section.pyi +16 -16
  161. reflex/components/radix/themes/layout/spacer.pyi +16 -16
  162. reflex/components/radix/themes/layout/stack.py +2 -2
  163. reflex/components/radix/themes/layout/stack.pyi +46 -46
  164. reflex/components/radix/themes/typography/blockquote.pyi +16 -16
  165. reflex/components/radix/themes/typography/code.pyi +16 -16
  166. reflex/components/radix/themes/typography/heading.pyi +16 -16
  167. reflex/components/radix/themes/typography/link.py +1 -1
  168. reflex/components/radix/themes/typography/link.pyi +16 -16
  169. reflex/components/radix/themes/typography/text.py +2 -2
  170. reflex/components/radix/themes/typography/text.pyi +106 -106
  171. reflex/components/react_player/audio.pyi +33 -39
  172. reflex/components/react_player/react_player.py +1 -1
  173. reflex/components/react_player/react_player.pyi +32 -38
  174. reflex/components/react_player/video.pyi +33 -39
  175. reflex/components/recharts/__init__.py +2 -0
  176. reflex/components/recharts/__init__.pyi +2 -0
  177. reflex/components/recharts/cartesian.pyi +282 -282
  178. reflex/components/recharts/charts.py +15 -15
  179. reflex/components/recharts/charts.pyi +164 -164
  180. reflex/components/recharts/general.py +19 -4
  181. reflex/components/recharts/general.pyi +132 -81
  182. reflex/components/recharts/polar.py +2 -2
  183. reflex/components/recharts/polar.pyi +55 -55
  184. reflex/components/recharts/recharts.py +4 -4
  185. reflex/components/recharts/recharts.pyi +31 -31
  186. reflex/components/sonner/toast.py +15 -13
  187. reflex/components/sonner/toast.pyi +22 -22
  188. reflex/components/suneditor/editor.py +6 -4
  189. reflex/components/suneditor/editor.pyi +26 -40
  190. reflex/components/tags/iter_tag.py +3 -3
  191. reflex/components/tags/tag.py +25 -3
  192. reflex/config.py +48 -20
  193. reflex/constants/__init__.py +1 -0
  194. reflex/constants/base.py +4 -1
  195. reflex/constants/compiler.py +5 -2
  196. reflex/constants/config.py +8 -1
  197. reflex/constants/installer.py +9 -9
  198. reflex/constants/style.py +1 -1
  199. reflex/custom_components/custom_components.py +18 -10
  200. reflex/event.py +228 -233
  201. reflex/experimental/__init__.py +19 -11
  202. reflex/experimental/client_state.py +53 -28
  203. reflex/experimental/hooks.py +5 -5
  204. reflex/experimental/layout.py +8 -5
  205. reflex/experimental/layout.pyi +79 -83
  206. reflex/experimental/misc.py +3 -3
  207. reflex/istate/wrappers.py +1 -1
  208. reflex/middleware/hydrate_middleware.py +2 -2
  209. reflex/model.py +11 -6
  210. reflex/page.py +5 -5
  211. reflex/reflex.py +104 -26
  212. reflex/route.py +1 -1
  213. reflex/state.py +358 -401
  214. reflex/style.py +27 -3
  215. reflex/testing.py +34 -39
  216. reflex/utils/build.py +6 -2
  217. reflex/utils/codespaces.py +1 -4
  218. reflex/utils/compat.py +6 -5
  219. reflex/utils/console.py +71 -21
  220. reflex/utils/exceptions.py +89 -26
  221. reflex/utils/exec.py +69 -74
  222. reflex/utils/export.py +6 -1
  223. reflex/utils/format.py +8 -40
  224. reflex/utils/imports.py +5 -2
  225. reflex/utils/lazy_loader.py +7 -1
  226. reflex/utils/path_ops.py +74 -14
  227. reflex/utils/prerequisites.py +345 -68
  228. reflex/utils/processes.py +45 -32
  229. reflex/utils/pyi_generator.py +39 -33
  230. reflex/utils/registry.py +4 -4
  231. reflex/utils/serializers.py +1 -1
  232. reflex/utils/telemetry.py +5 -4
  233. reflex/utils/types.py +42 -18
  234. reflex/vars/base.py +695 -330
  235. reflex/vars/datetime.py +6 -7
  236. reflex/vars/dep_tracking.py +344 -0
  237. reflex/vars/function.py +11 -5
  238. reflex/vars/number.py +31 -43
  239. reflex/vars/object.py +74 -64
  240. reflex/vars/sequence.py +79 -67
  241. {reflex-0.6.8a1.dist-info → reflex-0.7.0.dist-info}/METADATA +7 -10
  242. reflex-0.7.0.dist-info/RECORD +401 -0
  243. {reflex-0.6.8a1.dist-info → reflex-0.7.0.dist-info}/WHEEL +1 -1
  244. reflex/experimental/assets.py +0 -37
  245. reflex/proxy.py +0 -119
  246. reflex-0.6.8a1.dist-info/RECORD +0 -398
  247. {reflex-0.6.8a1.dist-info → reflex-0.7.0.dist-info}/LICENSE +0 -0
  248. {reflex-0.6.8a1.dist-info → reflex-0.7.0.dist-info}/entry_points.txt +0 -0
@@ -7,6 +7,7 @@ import dataclasses
7
7
  import functools
8
8
  import importlib
9
9
  import importlib.metadata
10
+ import importlib.util
10
11
  import json
11
12
  import os
12
13
  import platform
@@ -17,11 +18,12 @@ import stat
17
18
  import sys
18
19
  import tempfile
19
20
  import time
21
+ import typing
20
22
  import zipfile
21
23
  from datetime import datetime
22
24
  from pathlib import Path
23
25
  from types import ModuleType
24
- from typing import Callable, List, Optional
26
+ from typing import Any, Callable, List, NamedTuple, Optional
25
27
 
26
28
  import httpx
27
29
  import typer
@@ -36,15 +38,25 @@ from reflex.compiler import templates
36
38
  from reflex.config import Config, environment, get_config
37
39
  from reflex.utils import console, net, path_ops, processes, redir
38
40
  from reflex.utils.exceptions import (
39
- GeneratedCodeHasNoFunctionDefs,
40
- raise_system_package_missing_error,
41
+ GeneratedCodeHasNoFunctionDefsError,
42
+ SystemPackageMissingError,
41
43
  )
42
44
  from reflex.utils.format import format_library_name
43
45
  from reflex.utils.registry import _get_npm_registry
44
46
 
47
+ if typing.TYPE_CHECKING:
48
+ from reflex.app import App
49
+
45
50
  CURRENTLY_INSTALLING_NODE = False
46
51
 
47
52
 
53
+ class AppInfo(NamedTuple):
54
+ """A tuple containing the app instance and module."""
55
+
56
+ app: App
57
+ module: ModuleType
58
+
59
+
48
60
  @dataclasses.dataclass(frozen=True)
49
61
  class Template:
50
62
  """A template for a Reflex app."""
@@ -52,7 +64,7 @@ class Template:
52
64
  name: str
53
65
  description: str
54
66
  code_url: str
55
- demo_url: str
67
+ demo_url: str | None = None
56
68
 
57
69
 
58
70
  @dataclasses.dataclass(frozen=True)
@@ -75,16 +87,15 @@ def get_web_dir() -> Path:
75
87
  return environment.REFLEX_WEB_WORKDIR.get()
76
88
 
77
89
 
78
- def _python_version_check():
79
- """Emit deprecation warning for deprecated python versions."""
80
- # Check for end-of-life python versions.
81
- if sys.version_info < (3, 10):
82
- console.deprecate(
83
- feature_name="Support for Python 3.9 and older",
84
- reason="please upgrade to Python 3.10 or newer",
85
- deprecation_version="0.6.0",
86
- removal_version="0.7.0",
87
- )
90
+ def get_states_dir() -> Path:
91
+ """Get the working directory for the states.
92
+
93
+ Can be overridden with REFLEX_STATES_WORKDIR.
94
+
95
+ Returns:
96
+ The working directory.
97
+ """
98
+ return environment.REFLEX_STATES_WORKDIR.get()
88
99
 
89
100
 
90
101
  def check_latest_package_version(package_name: str):
@@ -109,8 +120,6 @@ def check_latest_package_version(package_name: str):
109
120
  console.warn(
110
121
  f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
111
122
  )
112
- # Check for deprecated python versions
113
- _python_version_check()
114
123
  except Exception:
115
124
  pass
116
125
 
@@ -167,7 +176,7 @@ def get_node_version() -> version.Version | None:
167
176
  try:
168
177
  result = processes.new_process([node_path, "-v"], run=True)
169
178
  # The output will be in the form "vX.Y.Z", but version.parse() can handle it
170
- return version.parse(result.stdout) # type: ignore
179
+ return version.parse(result.stdout) # pyright: ignore [reportArgumentType]
171
180
  except (FileNotFoundError, TypeError):
172
181
  return None
173
182
 
@@ -180,7 +189,7 @@ def get_fnm_version() -> version.Version | None:
180
189
  """
181
190
  try:
182
191
  result = processes.new_process([constants.Fnm.EXE, "--version"], run=True)
183
- return version.parse(result.stdout.split(" ")[1]) # type: ignore
192
+ return version.parse(result.stdout.split(" ")[1]) # pyright: ignore [reportOptionalMemberAccess, reportAttributeAccessIssue]
184
193
  except (FileNotFoundError, TypeError):
185
194
  return None
186
195
  except version.InvalidVersion as e:
@@ -196,10 +205,13 @@ def get_bun_version() -> version.Version | None:
196
205
  Returns:
197
206
  The version of bun.
198
207
  """
208
+ bun_path = path_ops.get_bun_path()
209
+ if bun_path is None:
210
+ return None
199
211
  try:
200
212
  # Run the bun -v command and capture the output
201
- result = processes.new_process([str(get_config().bun_path), "-v"], run=True)
202
- return version.parse(result.stdout) # type: ignore
213
+ result = processes.new_process([str(bun_path), "-v"], run=True)
214
+ return version.parse(str(result.stdout)) # pyright: ignore [reportArgumentType]
203
215
  except FileNotFoundError:
204
216
  return None
205
217
  except version.InvalidVersion as e:
@@ -243,7 +255,7 @@ def get_package_manager(on_failure_return_none: bool = False) -> str | None:
243
255
  """
244
256
  npm_path = path_ops.get_npm_path()
245
257
  if npm_path is not None:
246
- return str(Path(npm_path).resolve())
258
+ return str(npm_path)
247
259
  if on_failure_return_none:
248
260
  return None
249
261
  raise FileNotFoundError("NPM not found. You may need to run `reflex init`.")
@@ -267,6 +279,22 @@ def windows_npm_escape_hatch() -> bool:
267
279
  return environment.REFLEX_USE_NPM.get()
268
280
 
269
281
 
282
+ def _check_app_name(config: Config):
283
+ """Check if the app name is set in the config.
284
+
285
+ Args:
286
+ config: The config object.
287
+
288
+ Raises:
289
+ RuntimeError: If the app name is not set in the config.
290
+ """
291
+ if not config.app_name:
292
+ raise RuntimeError(
293
+ "Cannot get the app module because `app_name` is not set in rxconfig! "
294
+ "If this error occurs in a reflex test case, ensure that `get_app` is mocked."
295
+ )
296
+
297
+
270
298
  def get_app(reload: bool = False) -> ModuleType:
271
299
  """Get the app module based on the default config.
272
300
 
@@ -277,22 +305,23 @@ def get_app(reload: bool = False) -> ModuleType:
277
305
  The app based on the default config.
278
306
 
279
307
  Raises:
280
- RuntimeError: If the app name is not set in the config.
308
+ Exception: If an error occurs while getting the app module.
281
309
  """
282
310
  from reflex.utils import telemetry
283
311
 
284
312
  try:
285
313
  environment.RELOAD_CONFIG.set(reload)
286
314
  config = get_config()
287
- if not config.app_name:
288
- raise RuntimeError(
289
- "Cannot get the app module because `app_name` is not set in rxconfig! "
290
- "If this error occurs in a reflex test case, ensure that `get_app` is mocked."
291
- )
315
+
316
+ _check_app_name(config)
317
+
292
318
  module = config.module
293
319
  sys.path.insert(0, str(Path.cwd()))
294
- app = __import__(module, fromlist=(constants.CompileVars.APP,))
295
-
320
+ app = (
321
+ __import__(module, fromlist=(constants.CompileVars.APP,))
322
+ if not config.app_module
323
+ else config.app_module
324
+ )
296
325
  if reload:
297
326
  from reflex.state import reload_state_module
298
327
 
@@ -301,11 +330,34 @@ def get_app(reload: bool = False) -> ModuleType:
301
330
 
302
331
  # Reload the app module.
303
332
  importlib.reload(app)
304
-
305
- return app
306
333
  except Exception as ex:
307
334
  telemetry.send_error(ex, context="frontend")
308
335
  raise
336
+ else:
337
+ return app
338
+
339
+
340
+ def get_and_validate_app(reload: bool = False) -> AppInfo:
341
+ """Get the app instance based on the default config and validate it.
342
+
343
+ Args:
344
+ reload: Re-import the app module from disk
345
+
346
+ Returns:
347
+ The app instance and the app module.
348
+
349
+ Raises:
350
+ RuntimeError: If the app instance is not an instance of rx.App.
351
+ """
352
+ from reflex.app import App
353
+
354
+ app_module = get_app(reload=reload)
355
+ app = getattr(app_module, constants.CompileVars.APP)
356
+ if not isinstance(app, App):
357
+ raise RuntimeError(
358
+ "The app instance in the specified app_module_import in rxconfig must be an instance of rx.App."
359
+ )
360
+ return AppInfo(app=app, module=app_module)
309
361
 
310
362
 
311
363
  def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
@@ -318,8 +370,7 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
318
370
  Returns:
319
371
  The compiled app based on the default config.
320
372
  """
321
- app_module = get_app(reload=reload)
322
- app = getattr(app_module, constants.CompileVars.APP)
373
+ app, app_module = get_and_validate_app(reload=reload)
323
374
  # For py3.9 compatibility when redis is used, we MUST add any decorator pages
324
375
  # before compiling the app in a thread to avoid event loop error (REF-2172).
325
376
  app._apply_decorated_pages()
@@ -427,6 +478,167 @@ def validate_app_name(app_name: str | None = None) -> str:
427
478
  return app_name
428
479
 
429
480
 
481
+ def rename_path_up_tree(full_path: str | Path, old_name: str, new_name: str) -> Path:
482
+ """Rename all instances of `old_name` in the path (file and directories) to `new_name`.
483
+ The renaming stops when we reach the directory containing `rxconfig.py`.
484
+
485
+ Args:
486
+ full_path: The full path to start renaming from.
487
+ old_name: The name to be replaced.
488
+ new_name: The replacement name.
489
+
490
+ Returns:
491
+ The updated path after renaming.
492
+ """
493
+ current_path = Path(full_path)
494
+ new_path = None
495
+
496
+ while True:
497
+ directory, base = current_path.parent, current_path.name
498
+ # Stop renaming when we reach the root dir (which contains rxconfig.py)
499
+ if current_path.is_dir() and (current_path / "rxconfig.py").exists():
500
+ new_path = current_path
501
+ break
502
+
503
+ if old_name == base.removesuffix(constants.Ext.PY):
504
+ new_base = base.replace(old_name, new_name)
505
+ new_path = directory / new_base
506
+ current_path.rename(new_path)
507
+ console.debug(f"Renamed {current_path} -> {new_path}")
508
+ current_path = new_path
509
+ else:
510
+ new_path = current_path
511
+
512
+ # Move up the directory tree
513
+ current_path = directory
514
+
515
+ return new_path
516
+
517
+
518
+ def rename_app(new_app_name: str, loglevel: constants.LogLevel):
519
+ """Rename the app directory.
520
+
521
+ Args:
522
+ new_app_name: The new name for the app.
523
+ loglevel: The log level to use.
524
+
525
+ Raises:
526
+ Exit: If the command is not ran in the root dir or the app module cannot be imported.
527
+ """
528
+ # Set the log level.
529
+ console.set_log_level(loglevel)
530
+
531
+ if not constants.Config.FILE.exists():
532
+ console.error(
533
+ "No rxconfig.py found. Make sure you are in the root directory of your app."
534
+ )
535
+ raise typer.Exit(1)
536
+
537
+ sys.path.insert(0, str(Path.cwd()))
538
+
539
+ config = get_config()
540
+ module_path = importlib.util.find_spec(config.module)
541
+ if module_path is None:
542
+ console.error(f"Could not find module {config.module}.")
543
+ raise typer.Exit(1)
544
+
545
+ if not module_path.origin:
546
+ console.error(f"Could not find origin for module {config.module}.")
547
+ raise typer.Exit(1)
548
+ console.info(f"Renaming app directory to {new_app_name}.")
549
+ process_directory(
550
+ Path.cwd(),
551
+ config.app_name,
552
+ new_app_name,
553
+ exclude_dirs=[constants.Dirs.WEB, constants.Dirs.APP_ASSETS],
554
+ )
555
+
556
+ rename_path_up_tree(Path(module_path.origin), config.app_name, new_app_name)
557
+
558
+ console.success(f"App directory renamed to [bold]{new_app_name}[/bold].")
559
+
560
+
561
+ def rename_imports_and_app_name(file_path: str | Path, old_name: str, new_name: str):
562
+ """Rename imports the file using string replacement as well as app_name in rxconfig.py.
563
+
564
+ Args:
565
+ file_path: The file to process.
566
+ old_name: The old name to replace.
567
+ new_name: The new name to use.
568
+ """
569
+ file_path = Path(file_path)
570
+ content = file_path.read_text()
571
+
572
+ # Replace `from old_name.` or `from old_name` with `from new_name`
573
+ content = re.sub(
574
+ rf"\bfrom {re.escape(old_name)}(\b|\.|\s)",
575
+ lambda match: f"from {new_name}{match.group(1)}",
576
+ content,
577
+ )
578
+
579
+ # Replace `import old_name` with `import new_name`
580
+ content = re.sub(
581
+ rf"\bimport {re.escape(old_name)}\b",
582
+ f"import {new_name}",
583
+ content,
584
+ )
585
+
586
+ # Replace `app_name="old_name"` in rx.Config
587
+ content = re.sub(
588
+ rf'\bapp_name\s*=\s*["\']{re.escape(old_name)}["\']',
589
+ f'app_name="{new_name}"',
590
+ content,
591
+ )
592
+
593
+ # Replace positional argument `"old_name"` in rx.Config
594
+ content = re.sub(
595
+ rf'\brx\.Config\(\s*["\']{re.escape(old_name)}["\']',
596
+ f'rx.Config("{new_name}"',
597
+ content,
598
+ )
599
+
600
+ file_path.write_text(content)
601
+
602
+
603
+ def process_directory(
604
+ directory: str | Path,
605
+ old_name: str,
606
+ new_name: str,
607
+ exclude_dirs: list | None = None,
608
+ extensions: list | None = None,
609
+ ):
610
+ """Process files with specified extensions in a directory, excluding specified directories.
611
+
612
+ Args:
613
+ directory: The root directory to process.
614
+ old_name: The old name to replace.
615
+ new_name: The new name to use.
616
+ exclude_dirs: List of directory names to exclude. Defaults to None.
617
+ extensions: List of file extensions to process.
618
+ """
619
+ exclude_dirs = exclude_dirs or []
620
+ extensions = extensions or [
621
+ constants.Ext.PY,
622
+ constants.Ext.MD,
623
+ ] # include .md files, typically used in reflex-web.
624
+ extensions_set = {ext.lstrip(".") for ext in extensions}
625
+ directory = Path(directory)
626
+
627
+ root_exclude_dirs = {directory / exclude_dir for exclude_dir in exclude_dirs}
628
+
629
+ files = (
630
+ p.resolve()
631
+ for p in directory.glob("**/*")
632
+ if p.is_file() and p.suffix.lstrip(".") in extensions_set
633
+ )
634
+
635
+ for file_path in files:
636
+ if not any(
637
+ file_path.is_relative_to(exclude_dir) for exclude_dir in root_exclude_dirs
638
+ ):
639
+ rename_imports_and_app_name(file_path, old_name, new_name)
640
+
641
+
430
642
  def create_config(app_name: str):
431
643
  """Create a new rxconfig file.
432
644
 
@@ -610,10 +822,14 @@ def initialize_web_directory():
610
822
  init_reflex_json(project_hash=project_hash)
611
823
 
612
824
 
825
+ def _turbopack_flag() -> str:
826
+ return " --turbopack" if environment.REFLEX_USE_TURBOPACK.get() else ""
827
+
828
+
613
829
  def _compile_package_json():
614
830
  return templates.PACKAGE_JSON.render(
615
831
  scripts={
616
- "dev": constants.PackageJson.Commands.DEV,
832
+ "dev": constants.PackageJson.Commands.DEV + _turbopack_flag(),
617
833
  "export": constants.PackageJson.Commands.EXPORT,
618
834
  "export_sitemap": constants.PackageJson.Commands.EXPORT_SITEMAP,
619
835
  "prod": constants.PackageJson.Commands.PROD,
@@ -668,7 +884,9 @@ def init_reflex_json(project_hash: int | None):
668
884
  path_ops.update_json_file(get_web_dir() / constants.Reflex.JSON, reflex_json)
669
885
 
670
886
 
671
- def update_next_config(export=False, transpile_packages: Optional[List[str]] = None):
887
+ def update_next_config(
888
+ export: bool = False, transpile_packages: Optional[List[str]] = None
889
+ ):
672
890
  """Update Next.js config from Reflex config.
673
891
 
674
892
  Args:
@@ -694,7 +912,6 @@ def _update_next_config(
694
912
  next_config = {
695
913
  "basePath": config.frontend_path or "",
696
914
  "compress": config.next_compression,
697
- "reactStrictMode": config.react_strict_mode,
698
915
  "trailingSlash": True,
699
916
  "staticPageGenerationTimeout": config.static_page_generation_timeout,
700
917
  }
@@ -831,7 +1048,11 @@ def install_node():
831
1048
 
832
1049
 
833
1050
  def install_bun():
834
- """Install bun onto the user's system."""
1051
+ """Install bun onto the user's system.
1052
+
1053
+ Raises:
1054
+ SystemPackageMissingError: If "unzip" is missing.
1055
+ """
835
1056
  win_supported = is_windows_bun_supported()
836
1057
  one_drive_in_path = windows_check_onedrive_in_path()
837
1058
  if constants.IS_WINDOWS and (not win_supported or one_drive_in_path):
@@ -845,9 +1066,7 @@ def install_bun():
845
1066
  )
846
1067
 
847
1068
  # Skip if bun is already installed.
848
- if Path(get_config().bun_path).exists() and get_bun_version() == version.parse(
849
- constants.Bun.VERSION
850
- ):
1069
+ if get_bun_version() == version.parse(constants.Bun.VERSION):
851
1070
  console.debug("Skipping bun installation as it is already installed.")
852
1071
  return
853
1072
 
@@ -868,15 +1087,15 @@ def install_bun():
868
1087
  show_logs=console.is_debug(),
869
1088
  )
870
1089
  else:
871
- unzip_path = path_ops.which("unzip")
872
- if unzip_path is None:
873
- raise_system_package_missing_error("unzip")
1090
+ if path_ops.which("unzip") is None:
1091
+ raise SystemPackageMissingError("unzip")
874
1092
 
875
1093
  # Run the bun install script.
876
1094
  download_and_run(
877
1095
  constants.Bun.INSTALL_URL,
878
1096
  f"bun-v{constants.Bun.VERSION}",
879
1097
  BUN_INSTALL=str(constants.Bun.ROOT_PATH),
1098
+ BUN_VERSION=str(constants.Bun.VERSION),
880
1099
  )
881
1100
 
882
1101
 
@@ -910,7 +1129,7 @@ def cached_procedure(cache_file: str, payload_fn: Callable[..., str]):
910
1129
  The decorated function.
911
1130
  """
912
1131
 
913
- def _inner_decorator(func):
1132
+ def _inner_decorator(func: Callable):
914
1133
  def _inner(*args, **kwargs):
915
1134
  payload = _read_cached_procedure_file(cache_file)
916
1135
  new_payload = payload_fn(*args, **kwargs)
@@ -970,7 +1189,7 @@ def install_frontend_packages(packages: set[str], config: Config):
970
1189
  )
971
1190
 
972
1191
  processes.run_process_with_fallback(
973
- [install_package_manager, "install"], # type: ignore
1192
+ [install_package_manager, "install"],
974
1193
  fallback=fallback_command,
975
1194
  analytics_enabled=True,
976
1195
  show_status_message="Installing base frontend packages",
@@ -1006,6 +1225,21 @@ def install_frontend_packages(packages: set[str], config: Config):
1006
1225
  )
1007
1226
 
1008
1227
 
1228
+ def check_running_mode(frontend: bool, backend: bool) -> tuple[bool, bool]:
1229
+ """Check if the app is running in frontend or backend mode.
1230
+
1231
+ Args:
1232
+ frontend: Whether to run the frontend of the app.
1233
+ backend: Whether to run the backend of the app.
1234
+
1235
+ Returns:
1236
+ The running modes.
1237
+ """
1238
+ if not frontend and not backend:
1239
+ return True, True
1240
+ return frontend, backend
1241
+
1242
+
1009
1243
  def needs_reinit(frontend: bool = True) -> bool:
1010
1244
  """Check if an app needs to be reinitialized.
1011
1245
 
@@ -1072,15 +1306,15 @@ def validate_bun():
1072
1306
  Raises:
1073
1307
  Exit: If custom specified bun does not exist or does not meet requirements.
1074
1308
  """
1075
- # if a custom bun path is provided, make sure its valid
1076
- # This is specific to non-FHS OS
1077
- bun_path = get_config().bun_path
1078
- if path_ops.use_system_bun():
1079
- bun_path = path_ops.which("bun")
1080
- if bun_path != constants.Bun.DEFAULT_PATH:
1309
+ bun_path = path_ops.get_bun_path()
1310
+
1311
+ if bun_path is None:
1312
+ return
1313
+
1314
+ if not path_ops.samefile(bun_path, constants.Bun.DEFAULT_PATH):
1081
1315
  console.info(f"Using custom Bun path: {bun_path}")
1082
1316
  bun_version = get_bun_version()
1083
- if not bun_version:
1317
+ if bun_version is None:
1084
1318
  console.error(
1085
1319
  "Failed to obtain bun version. Make sure the specified bun path in your config is correct."
1086
1320
  )
@@ -1095,7 +1329,7 @@ def validate_bun():
1095
1329
  raise typer.Exit(1)
1096
1330
 
1097
1331
 
1098
- def validate_frontend_dependencies(init=True):
1332
+ def validate_frontend_dependencies(init: bool = True):
1099
1333
  """Validate frontend dependencies to ensure they meet requirements.
1100
1334
 
1101
1335
  Args:
@@ -1149,11 +1383,12 @@ def ensure_reflex_installation_id() -> Optional[int]:
1149
1383
  if installation_id is None:
1150
1384
  installation_id = random.getrandbits(128)
1151
1385
  installation_id_file.write_text(str(installation_id))
1152
- # If we get here, installation_id is definitely set
1153
- return installation_id
1154
1386
  except Exception as e:
1155
1387
  console.debug(f"Failed to ensure reflex installation id: {e}")
1156
1388
  return None
1389
+ else:
1390
+ # If we get here, installation_id is definitely set
1391
+ return installation_id
1157
1392
 
1158
1393
 
1159
1394
  def initialize_reflex_user_directory():
@@ -1244,7 +1479,7 @@ def prompt_for_template_options(templates: list[Template]) -> str:
1244
1479
  # Show the user the URLs of each template to preview.
1245
1480
  console.print("\nGet started with a template:")
1246
1481
 
1247
- def format_demo_url_str(url: str) -> str:
1482
+ def format_demo_url_str(url: str | None) -> str:
1248
1483
  return f" ({url})" if url else ""
1249
1484
 
1250
1485
  # Prompt the user to select a template.
@@ -1265,7 +1500,7 @@ def prompt_for_template_options(templates: list[Template]) -> str:
1265
1500
  )
1266
1501
 
1267
1502
  # Return the template.
1268
- return templates[int(template)].name
1503
+ return templates[int(template)].name # pyright: ignore [reportArgumentType]
1269
1504
 
1270
1505
 
1271
1506
  def fetch_app_templates(version: str) -> dict[str, Template]:
@@ -1367,19 +1602,22 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
1367
1602
  except OSError as ose:
1368
1603
  console.error(f"Failed to create temp directory for extracting zip: {ose}")
1369
1604
  raise typer.Exit(1) from ose
1605
+
1370
1606
  try:
1371
1607
  zipfile.ZipFile(zip_file_path).extractall(path=unzip_dir)
1372
1608
  # The zip file downloaded from github looks like:
1373
1609
  # repo-name-branch/**/*, so we need to remove the top level directory.
1374
- if len(subdirs := os.listdir(unzip_dir)) != 1:
1375
- console.error(f"Expected one directory in the zip, found {subdirs}")
1376
- raise typer.Exit(1)
1377
- template_dir = unzip_dir / subdirs[0]
1378
- console.debug(f"Template folder is located at {template_dir}")
1379
1610
  except Exception as uze:
1380
1611
  console.error(f"Failed to unzip the template: {uze}")
1381
1612
  raise typer.Exit(1) from uze
1382
1613
 
1614
+ if len(subdirs := os.listdir(unzip_dir)) != 1:
1615
+ console.error(f"Expected one directory in the zip, found {subdirs}")
1616
+ raise typer.Exit(1)
1617
+
1618
+ template_dir = unzip_dir / subdirs[0]
1619
+ console.debug(f"Template folder is located at {template_dir}")
1620
+
1383
1621
  # Move the rxconfig file here first.
1384
1622
  path_ops.mv(str(template_dir / constants.Config.FILE), constants.Config.FILE)
1385
1623
  new_config = get_config(reload=True)
@@ -1415,7 +1653,9 @@ def initialize_default_app(app_name: str):
1415
1653
  initialize_app_directory(app_name)
1416
1654
 
1417
1655
 
1418
- def validate_and_create_app_using_remote_template(app_name, template, templates):
1656
+ def validate_and_create_app_using_remote_template(
1657
+ app_name: str, template: str, templates: dict[str, Template]
1658
+ ):
1419
1659
  """Validate and create an app using a remote template.
1420
1660
 
1421
1661
  Args:
@@ -1605,7 +1845,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
1605
1845
  generation_hash: The generation hash from reflex.build.
1606
1846
 
1607
1847
  Raises:
1608
- GeneratedCodeHasNoFunctionDefs: If the fetched code has no function definitions
1848
+ GeneratedCodeHasNoFunctionDefsError: If the fetched code has no function definitions
1609
1849
  (the refactored reflex code is expected to have at least one root function defined).
1610
1850
  """
1611
1851
  # Download the reflex code for the generation.
@@ -1622,17 +1862,17 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
1622
1862
  # Determine the name of the last function, which renders the generated code.
1623
1863
  defined_funcs = re.findall(r"def ([a-zA-Z_]+)\(", resp.text)
1624
1864
  if not defined_funcs:
1625
- raise GeneratedCodeHasNoFunctionDefs(
1865
+ raise GeneratedCodeHasNoFunctionDefsError(
1626
1866
  f"No function definitions found in generated code from {url!r}."
1627
1867
  )
1628
1868
  render_func_name = defined_funcs[-1]
1629
1869
 
1630
- def replace_content(_match):
1870
+ def replace_content(_match: re.Match) -> str:
1631
1871
  return "\n".join(
1632
1872
  [
1633
1873
  resp.text,
1634
1874
  "",
1635
- "" "def index() -> rx.Component:",
1875
+ "def index() -> rx.Component:",
1636
1876
  f" return {render_func_name}()",
1637
1877
  "",
1638
1878
  "",
@@ -1657,7 +1897,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
1657
1897
  main_module_path.write_text(main_module_code)
1658
1898
 
1659
1899
 
1660
- def format_address_width(address_width) -> int | None:
1900
+ def format_address_width(address_width: str | None) -> int | None:
1661
1901
  """Cast address width to an int.
1662
1902
 
1663
1903
  Args:
@@ -1756,3 +1996,40 @@ def is_generation_hash(template: str) -> bool:
1756
1996
  True if the template is composed of 32 or more hex characters.
1757
1997
  """
1758
1998
  return re.match(r"^[0-9a-f]{32,}$", template) is not None
1999
+
2000
+
2001
+ def check_config_option_in_tier(
2002
+ option_name: str,
2003
+ allowed_tiers: list[str],
2004
+ fallback_value: Any,
2005
+ help_link: str | None = None,
2006
+ ):
2007
+ """Check if a config option is allowed for the authenticated user's current tier.
2008
+
2009
+ Args:
2010
+ option_name: The name of the option to check.
2011
+ allowed_tiers: The tiers that are allowed to use the option.
2012
+ fallback_value: The fallback value if the option is not allowed.
2013
+ help_link: The help link to show to a user that is authenticated.
2014
+ """
2015
+ from reflex_cli.v2.utils import hosting
2016
+
2017
+ config = get_config()
2018
+ authenticated_token = hosting.authenticated_token()
2019
+ if not authenticated_token[0]:
2020
+ the_remedy = (
2021
+ "You are currently logged out. Run `reflex login` to access this option."
2022
+ )
2023
+ current_tier = "anonymous"
2024
+ else:
2025
+ current_tier = authenticated_token[1].get("tier", "").lower()
2026
+ the_remedy = (
2027
+ f"Your current subscription tier is `{current_tier}`. "
2028
+ f"Please upgrade to {allowed_tiers} to access this option. "
2029
+ )
2030
+ if help_link:
2031
+ the_remedy += f"See {help_link} for more information."
2032
+ if current_tier not in allowed_tiers:
2033
+ console.warn(f"Config option `{option_name}` is restricted. {the_remedy}")
2034
+ setattr(config, option_name, fallback_value)
2035
+ config._set_persistent(**{option_name: fallback_value})