reflex 0.7.14a6__py3-none-any.whl → 0.8.0a2__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 (211) hide show
  1. reflex/.templates/jinja/app/rxconfig.py.jinja2 +4 -1
  2. reflex/.templates/jinja/web/package.json.jinja2 +1 -1
  3. reflex/.templates/jinja/web/pages/_app.js.jinja2 +16 -10
  4. reflex/.templates/jinja/web/pages/_document.js.jinja2 +1 -1
  5. reflex/.templates/jinja/web/pages/base_page.js.jinja2 +0 -1
  6. reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +4 -0
  7. reflex/.templates/jinja/web/utils/context.js.jinja2 +25 -8
  8. reflex/.templates/web/app/entry.client.js +8 -0
  9. reflex/.templates/web/app/routes.js +10 -0
  10. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +12 -37
  11. reflex/.templates/web/postcss.config.js +1 -1
  12. reflex/.templates/web/react-router.config.js +6 -0
  13. reflex/.templates/web/utils/client_side_routing.js +21 -19
  14. reflex/.templates/web/utils/react-theme.js +92 -0
  15. reflex/.templates/web/utils/state.js +160 -67
  16. reflex/.templates/web/vite.config.js +32 -0
  17. reflex/__init__.py +1 -6
  18. reflex/__init__.pyi +0 -4
  19. reflex/app.py +53 -116
  20. reflex/base.py +1 -87
  21. reflex/compiler/compiler.py +41 -8
  22. reflex/compiler/templates.py +3 -3
  23. reflex/compiler/utils.py +73 -33
  24. reflex/components/__init__.py +0 -2
  25. reflex/components/__init__.pyi +0 -3
  26. reflex/components/base/__init__.py +1 -5
  27. reflex/components/base/__init__.pyi +4 -6
  28. reflex/components/base/app_wrap.pyi +5 -4
  29. reflex/components/base/body.pyi +5 -4
  30. reflex/components/base/document.py +18 -14
  31. reflex/components/base/document.pyi +83 -27
  32. reflex/components/base/error_boundary.pyi +5 -4
  33. reflex/components/base/fragment.pyi +5 -4
  34. reflex/components/base/link.pyi +9 -7
  35. reflex/components/base/meta.pyi +17 -13
  36. reflex/components/base/script.py +60 -58
  37. reflex/components/base/script.pyi +246 -31
  38. reflex/components/base/strict_mode.pyi +5 -4
  39. reflex/components/component.py +146 -217
  40. reflex/components/core/__init__.py +1 -0
  41. reflex/components/core/__init__.pyi +1 -0
  42. reflex/components/core/auto_scroll.pyi +5 -4
  43. reflex/components/core/banner.pyi +25 -19
  44. reflex/components/core/client_side_routing.py +7 -6
  45. reflex/components/core/client_side_routing.pyi +6 -56
  46. reflex/components/core/clipboard.pyi +5 -4
  47. reflex/components/core/debounce.py +1 -0
  48. reflex/components/core/debounce.pyi +5 -4
  49. reflex/components/core/foreach.py +3 -2
  50. reflex/components/core/helmet.py +14 -0
  51. reflex/components/{next/base.pyi → core/helmet.pyi} +10 -7
  52. reflex/components/core/html.pyi +5 -4
  53. reflex/components/core/sticky.pyi +17 -13
  54. reflex/components/core/upload.py +2 -1
  55. reflex/components/core/upload.pyi +21 -16
  56. reflex/components/datadisplay/code.py +2 -72
  57. reflex/components/datadisplay/code.pyi +9 -10
  58. reflex/components/datadisplay/dataeditor.pyi +11 -6
  59. reflex/components/datadisplay/shiki_code_block.pyi +13 -10
  60. reflex/components/dynamic.py +5 -5
  61. reflex/components/el/element.pyi +5 -4
  62. reflex/components/el/elements/base.pyi +5 -4
  63. reflex/components/el/elements/forms.pyi +69 -52
  64. reflex/components/el/elements/inline.pyi +113 -85
  65. reflex/components/el/elements/media.pyi +105 -79
  66. reflex/components/el/elements/metadata.pyi +25 -19
  67. reflex/components/el/elements/other.pyi +29 -22
  68. reflex/components/el/elements/scripts.pyi +13 -10
  69. reflex/components/el/elements/sectioning.pyi +61 -46
  70. reflex/components/el/elements/tables.pyi +41 -31
  71. reflex/components/el/elements/typography.pyi +61 -46
  72. reflex/components/field.py +175 -0
  73. reflex/components/gridjs/datatable.py +2 -2
  74. reflex/components/gridjs/datatable.pyi +11 -9
  75. reflex/components/lucide/icon.py +6 -2
  76. reflex/components/lucide/icon.pyi +15 -10
  77. reflex/components/markdown/markdown.pyi +5 -4
  78. reflex/components/moment/moment.pyi +5 -4
  79. reflex/components/plotly/plotly.pyi +19 -10
  80. reflex/components/props.py +376 -27
  81. reflex/components/radix/primitives/accordion.py +8 -1
  82. reflex/components/radix/primitives/accordion.pyi +29 -22
  83. reflex/components/radix/primitives/base.pyi +9 -7
  84. reflex/components/radix/primitives/drawer.pyi +45 -34
  85. reflex/components/radix/primitives/form.pyi +41 -31
  86. reflex/components/radix/primitives/progress.pyi +21 -16
  87. reflex/components/radix/primitives/slider.pyi +21 -16
  88. reflex/components/radix/themes/base.py +3 -3
  89. reflex/components/radix/themes/base.pyi +33 -25
  90. reflex/components/radix/themes/color_mode.pyi +13 -10
  91. reflex/components/radix/themes/components/alert_dialog.pyi +29 -22
  92. reflex/components/radix/themes/components/aspect_ratio.pyi +5 -4
  93. reflex/components/radix/themes/components/avatar.pyi +5 -4
  94. reflex/components/radix/themes/components/badge.pyi +5 -4
  95. reflex/components/radix/themes/components/button.pyi +5 -4
  96. reflex/components/radix/themes/components/callout.pyi +21 -16
  97. reflex/components/radix/themes/components/card.pyi +5 -4
  98. reflex/components/radix/themes/components/checkbox.pyi +13 -10
  99. reflex/components/radix/themes/components/checkbox_cards.pyi +9 -7
  100. reflex/components/radix/themes/components/checkbox_group.pyi +9 -7
  101. reflex/components/radix/themes/components/context_menu.pyi +53 -40
  102. reflex/components/radix/themes/components/data_list.pyi +17 -13
  103. reflex/components/radix/themes/components/dialog.pyi +29 -22
  104. reflex/components/radix/themes/components/dropdown_menu.pyi +33 -25
  105. reflex/components/radix/themes/components/hover_card.pyi +17 -13
  106. reflex/components/radix/themes/components/icon_button.pyi +5 -4
  107. reflex/components/radix/themes/components/inset.pyi +5 -4
  108. reflex/components/radix/themes/components/popover.pyi +17 -13
  109. reflex/components/radix/themes/components/progress.pyi +5 -4
  110. reflex/components/radix/themes/components/radio.pyi +5 -4
  111. reflex/components/radix/themes/components/radio_cards.pyi +9 -7
  112. reflex/components/radix/themes/components/radio_group.pyi +17 -13
  113. reflex/components/radix/themes/components/scroll_area.pyi +5 -4
  114. reflex/components/radix/themes/components/segmented_control.pyi +9 -7
  115. reflex/components/radix/themes/components/select.pyi +37 -28
  116. reflex/components/radix/themes/components/separator.pyi +5 -4
  117. reflex/components/radix/themes/components/skeleton.pyi +5 -4
  118. reflex/components/radix/themes/components/slider.pyi +5 -4
  119. reflex/components/radix/themes/components/spinner.pyi +5 -4
  120. reflex/components/radix/themes/components/switch.pyi +5 -4
  121. reflex/components/radix/themes/components/table.pyi +29 -22
  122. reflex/components/radix/themes/components/tabs.pyi +21 -16
  123. reflex/components/radix/themes/components/text_area.pyi +5 -4
  124. reflex/components/radix/themes/components/text_field.pyi +13 -10
  125. reflex/components/radix/themes/components/tooltip.pyi +5 -4
  126. reflex/components/radix/themes/layout/base.pyi +5 -4
  127. reflex/components/radix/themes/layout/box.pyi +5 -4
  128. reflex/components/radix/themes/layout/center.pyi +5 -4
  129. reflex/components/radix/themes/layout/container.pyi +5 -4
  130. reflex/components/radix/themes/layout/flex.pyi +5 -4
  131. reflex/components/radix/themes/layout/grid.pyi +5 -4
  132. reflex/components/radix/themes/layout/list.pyi +21 -16
  133. reflex/components/radix/themes/layout/section.pyi +5 -4
  134. reflex/components/radix/themes/layout/spacer.pyi +5 -4
  135. reflex/components/radix/themes/layout/stack.pyi +13 -10
  136. reflex/components/radix/themes/typography/blockquote.pyi +5 -4
  137. reflex/components/radix/themes/typography/code.pyi +5 -4
  138. reflex/components/radix/themes/typography/heading.pyi +5 -4
  139. reflex/components/radix/themes/typography/link.py +46 -11
  140. reflex/components/radix/themes/typography/link.pyi +311 -6
  141. reflex/components/radix/themes/typography/text.pyi +29 -22
  142. reflex/components/react_player/audio.pyi +5 -4
  143. reflex/components/react_player/react_player.pyi +5 -4
  144. reflex/components/react_player/video.pyi +5 -4
  145. reflex/components/recharts/cartesian.py +2 -1
  146. reflex/components/recharts/cartesian.pyi +65 -46
  147. reflex/components/recharts/charts.py +4 -2
  148. reflex/components/recharts/charts.pyi +36 -24
  149. reflex/components/recharts/general.pyi +24 -18
  150. reflex/components/recharts/polar.py +8 -4
  151. reflex/components/recharts/polar.pyi +16 -10
  152. reflex/components/recharts/recharts.pyi +9 -7
  153. reflex/components/sonner/toast.py +2 -2
  154. reflex/components/sonner/toast.pyi +10 -8
  155. reflex/config.py +3 -77
  156. reflex/constants/__init__.py +2 -2
  157. reflex/constants/base.py +28 -11
  158. reflex/constants/compiler.py +5 -3
  159. reflex/constants/event.py +1 -0
  160. reflex/constants/installer.py +22 -16
  161. reflex/constants/route.py +19 -7
  162. reflex/constants/state.py +2 -0
  163. reflex/custom_components/custom_components.py +0 -14
  164. reflex/environment.py +1 -1
  165. reflex/event.py +178 -81
  166. reflex/experimental/__init__.py +0 -30
  167. reflex/istate/proxy.py +5 -3
  168. reflex/page.py +0 -27
  169. reflex/plugins/__init__.py +3 -2
  170. reflex/plugins/base.py +5 -1
  171. reflex/plugins/shared_tailwind.py +158 -0
  172. reflex/plugins/sitemap.py +206 -0
  173. reflex/plugins/tailwind_v3.py +13 -106
  174. reflex/plugins/tailwind_v4.py +15 -108
  175. reflex/reflex.py +1 -0
  176. reflex/route.py +15 -21
  177. reflex/state.py +134 -140
  178. reflex/testing.py +58 -10
  179. reflex/utils/build.py +38 -82
  180. reflex/utils/exec.py +59 -161
  181. reflex/utils/export.py +2 -2
  182. reflex/utils/imports.py +0 -4
  183. reflex/utils/misc.py +28 -0
  184. reflex/utils/prerequisites.py +65 -62
  185. reflex/utils/processes.py +8 -7
  186. reflex/utils/pyi_generator.py +21 -9
  187. reflex/utils/serializers.py +14 -1
  188. reflex/utils/types.py +196 -61
  189. reflex/vars/__init__.py +2 -0
  190. reflex/vars/base.py +367 -134
  191. {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/METADATA +12 -5
  192. {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/RECORD +195 -202
  193. reflex/.templates/web/next.config.js +0 -7
  194. reflex/components/base/head.py +0 -20
  195. reflex/components/base/head.pyi +0 -116
  196. reflex/components/next/__init__.py +0 -10
  197. reflex/components/next/base.py +0 -7
  198. reflex/components/next/image.py +0 -117
  199. reflex/components/next/image.pyi +0 -94
  200. reflex/components/next/link.py +0 -20
  201. reflex/components/next/link.pyi +0 -67
  202. reflex/components/next/video.py +0 -38
  203. reflex/components/next/video.pyi +0 -68
  204. reflex/components/suneditor/__init__.py +0 -5
  205. reflex/components/suneditor/editor.py +0 -269
  206. reflex/components/suneditor/editor.pyi +0 -199
  207. reflex/experimental/layout.py +0 -254
  208. reflex/experimental/layout.pyi +0 -814
  209. {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/WHEEL +0 -0
  210. {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/entry_points.txt +0 -0
  211. {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/licenses/LICENSE +0 -0
reflex/testing.py CHANGED
@@ -241,7 +241,7 @@ class AppHarness:
241
241
  def _initialize_app(self):
242
242
  # disable telemetry reporting for tests
243
243
 
244
- os.environ["TELEMETRY_ENABLED"] = "false"
244
+ os.environ["REFLEX_TELEMETRY_ENABLED"] = "false"
245
245
  CustomComponent.create().get_component.cache_clear()
246
246
  self.app_path.mkdir(parents=True, exist_ok=True)
247
247
  if self.app_source is not None:
@@ -344,8 +344,12 @@ class AppHarness:
344
344
  )
345
345
  self.backend.shutdown = self._get_backend_shutdown_handler()
346
346
  with chdir(self.app_path):
347
+ print( # noqa: T201
348
+ "Creating backend in a new thread..."
349
+ ) # for pytest diagnosis
347
350
  self.backend_thread = threading.Thread(target=self.backend.run)
348
351
  self.backend_thread.start()
352
+ print("Backend started.") # for pytest diagnosis #noqa: T201
349
353
 
350
354
  async def _reset_backend_state_manager(self):
351
355
  """Reset the StateManagerRedis event loop affinity.
@@ -377,11 +381,15 @@ class AppHarness:
377
381
  # Set up the frontend.
378
382
  with chdir(self.app_path):
379
383
  config = reflex.config.get_config()
384
+ print("Polling for servers...") # for pytest diagnosis #noqa: T201
380
385
  config.api_url = "http://{}:{}".format(
381
- *self._poll_for_servers().getsockname(),
386
+ *self._poll_for_servers(timeout=30).getsockname(),
382
387
  )
388
+ print("Building frontend...") # for pytest diagnosis #noqa: T201
383
389
  reflex.utils.build.setup_frontend(self.app_path)
384
390
 
391
+ print("Frontend starting...") # for pytest diagnosis #noqa: T201
392
+
385
393
  # Start the frontend.
386
394
  self.frontend_process = reflex.utils.processes.new_process(
387
395
  [
@@ -392,19 +400,20 @@ class AppHarness:
392
400
  "dev",
393
401
  ],
394
402
  cwd=self.app_path / reflex.utils.prerequisites.get_web_dir(),
395
- env={"PORT": "0"},
403
+ env={"PORT": "0", "NO_COLOR": "1"},
396
404
  **FRONTEND_POPEN_ARGS,
397
405
  )
398
406
 
399
407
  def _wait_frontend(self):
408
+ if self.frontend_process is None or self.frontend_process.stdout is None:
409
+ msg = "Frontend process has no stdout."
410
+ raise RuntimeError(msg)
400
411
  while self.frontend_url is None:
401
- line = (
402
- self.frontend_process.stdout.readline() # pyright: ignore [reportOptionalMemberAccess]
403
- )
412
+ line = self.frontend_process.stdout.readline()
404
413
  if not line:
405
414
  break
406
415
  print(line) # for pytest diagnosis #noqa: T201
407
- m = re.search(reflex.constants.Next.FRONTEND_LISTENING_REGEX, line)
416
+ m = re.search(reflex.constants.ReactRouter.FRONTEND_LISTENING_REGEX, line)
408
417
  if m is not None:
409
418
  self.frontend_url = m.group(1)
410
419
  config = reflex.config.get_config()
@@ -840,6 +849,37 @@ class AppHarness:
840
849
  raise TimeoutError(msg)
841
850
  return state_manager.states
842
851
 
852
+ @staticmethod
853
+ def poll_for_result(
854
+ f: Callable[[], T],
855
+ exception: type[Exception] = Exception,
856
+ max_attempts: int = 5,
857
+ seconds_between_attempts: int = 1,
858
+ ) -> T:
859
+ """Poll for a result from a function.
860
+
861
+ Args:
862
+ f: function to call
863
+ exception: exception to catch
864
+ max_attempts: maximum number of attempts
865
+ seconds_between_attempts: seconds to wait between
866
+
867
+ Returns:
868
+ Result of the function
869
+
870
+ Raises:
871
+ AssertionError: if the function does not return a value
872
+ """
873
+ attempts = 0
874
+ while attempts < max_attempts:
875
+ try:
876
+ return f()
877
+ except exception: # noqa: PERF203
878
+ attempts += 1
879
+ time.sleep(seconds_between_attempts)
880
+ msg = "Function did not return a value"
881
+ raise AssertionError(msg)
882
+
843
883
 
844
884
  class SimpleHTTPRequestHandlerCustomErrors(SimpleHTTPRequestHandler):
845
885
  """SimpleHTTPRequestHandler with custom error page handling."""
@@ -922,7 +962,7 @@ class Subdir404TCPServer(socketserver.TCPServer):
922
962
  class AppHarnessProd(AppHarness):
923
963
  """AppHarnessProd executes a reflex app in-process for testing.
924
964
 
925
- In prod mode, instead of running `next dev` the app is exported as static
965
+ In prod mode, instead of running `react-router dev` the app is exported as static
926
966
  files and served via the builtin python http.server with custom 404 redirect
927
967
  handling. Additionally, the backend runs in multi-worker mode.
928
968
  """
@@ -937,7 +977,7 @@ class AppHarnessProd(AppHarness):
937
977
  / reflex.constants.Dirs.STATIC
938
978
  )
939
979
  error_page_map = {
940
- 404: web_root / "404.html",
980
+ 404: web_root / "404" / "index.html",
941
981
  }
942
982
  with Subdir404TCPServer(
943
983
  ("", 0),
@@ -954,9 +994,11 @@ class AppHarnessProd(AppHarness):
954
994
  # Set up the frontend.
955
995
  with chdir(self.app_path):
956
996
  config = reflex.config.get_config()
997
+ print("Polling for servers...") # for pytest diagnosis #noqa: T201
957
998
  config.api_url = "http://{}:{}".format(
958
- *self._poll_for_servers().getsockname(),
999
+ *self._poll_for_servers(timeout=30).getsockname(),
959
1000
  )
1001
+ print("Building frontend...") # for pytest diagnosis #noqa: T201
960
1002
 
961
1003
  get_config().loglevel = reflex.constants.LogLevel.INFO
962
1004
 
@@ -973,6 +1015,8 @@ class AppHarnessProd(AppHarness):
973
1015
  env=reflex.constants.Env.PROD,
974
1016
  )
975
1017
 
1018
+ print("Frontend starting...") # for pytest diagnosis #noqa: T201
1019
+
976
1020
  self.frontend_thread = threading.Thread(target=self._run_frontend)
977
1021
  self.frontend_thread.start()
978
1022
 
@@ -996,8 +1040,12 @@ class AppHarnessProd(AppHarness):
996
1040
  ),
997
1041
  )
998
1042
  self.backend.shutdown = self._get_backend_shutdown_handler()
1043
+ print( # noqa: T201
1044
+ "Creating backend in a new thread..."
1045
+ )
999
1046
  self.backend_thread = threading.Thread(target=self.backend.run)
1000
1047
  self.backend_thread.start()
1048
+ print("Backend started.") # for pytest diagnosis #noqa: T201
1001
1049
 
1002
1050
  def _poll_for_servers(self, timeout: TimeoutType = None) -> socket.socket:
1003
1051
  try:
reflex/utils/build.py CHANGED
@@ -2,16 +2,13 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import json
6
5
  import os
7
- import subprocess
8
6
  import zipfile
9
7
  from pathlib import Path
10
8
 
11
9
  from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
12
10
 
13
11
  from reflex import constants
14
- from reflex.config import get_config
15
12
  from reflex.utils import console, path_ops, prerequisites, processes
16
13
  from reflex.utils.exec import is_in_app_harness
17
14
 
@@ -27,30 +24,6 @@ def set_env_json():
27
24
  )
28
25
 
29
26
 
30
- def generate_sitemap_config(deploy_url: str, export: bool = False):
31
- """Generate the sitemap config file.
32
-
33
- Args:
34
- deploy_url: The URL of the deployed app.
35
- export: If the sitemap are generated for an export.
36
- """
37
- # Import here to avoid circular imports.
38
- from reflex.compiler import templates
39
-
40
- config = {
41
- "siteUrl": deploy_url,
42
- "generateRobotsTxt": True,
43
- }
44
-
45
- if export:
46
- config["outDir"] = constants.Dirs.STATIC
47
-
48
- config = json.dumps(config)
49
-
50
- sitemap = prerequisites.get_web_dir() / constants.Next.SITEMAP_CONFIG_FILE
51
- sitemap.write_text(templates.SITEMAP_CONFIG(config=config))
52
-
53
-
54
27
  def _zip(
55
28
  component_name: constants.ComponentName,
56
29
  target: str | Path,
@@ -175,102 +148,85 @@ def zip_app(
175
148
  )
176
149
 
177
150
 
178
- def build(
179
- deploy_url: str | None = None,
180
- for_export: bool = False,
181
- ):
182
- """Build the app for deployment.
151
+ def _duplicate_index_html_to_parent_dir(directory: Path):
152
+ """Duplicate index.html in the child directories to the given directory.
153
+
154
+ This makes accessing /route and /route/ work in production.
183
155
 
184
156
  Args:
185
- deploy_url: The deployment URL.
186
- for_export: Whether the build is for export.
157
+ directory: The directory to duplicate index.html to.
187
158
  """
159
+ for child in directory.iterdir():
160
+ if child.is_dir():
161
+ # If the child directory has an index.html, copy it to the parent directory.
162
+ index_html = child / "index.html"
163
+ if index_html.exists():
164
+ target = directory / (child.name + ".html")
165
+ if not target.exists():
166
+ console.debug(f"Copying {index_html} to {target}")
167
+ path_ops.cp(index_html, target)
168
+ else:
169
+ console.debug(f"Skipping {index_html}, already exists at {target}")
170
+ # Recursively call this function for the child directory.
171
+ _duplicate_index_html_to_parent_dir(child)
172
+
173
+
174
+ def build():
175
+ """Build the app for deployment."""
188
176
  wdir = prerequisites.get_web_dir()
189
177
 
190
178
  # Clean the static directory if it exists.
191
- path_ops.rm(str(wdir / constants.Dirs.STATIC))
192
-
193
- # The export command to run.
194
- command = "export"
179
+ path_ops.rm(str(wdir / constants.Dirs.BUILD_DIR))
195
180
 
196
181
  checkpoints = [
197
- "Linting and checking ",
198
- "Creating an optimized production build",
199
- "Route (pages)",
200
- "prerendered as static HTML",
201
- "Collecting page data",
202
- "Finalizing page optimization",
203
- "Collecting build traces",
182
+ "building for production",
183
+ "building SSR bundle for production",
184
+ "built in",
204
185
  ]
205
186
 
206
- # Generate a sitemap if a deploy URL is provided.
207
- if deploy_url is not None:
208
- generate_sitemap_config(deploy_url, export=for_export)
209
- command = "export-sitemap"
210
-
211
- checkpoints.extend(["Loading next-sitemap", "Generation completed"])
212
-
213
187
  # Start the subprocess with the progress bar.
214
188
  process = processes.new_process(
215
- [*prerequisites.get_js_package_executor(raise_on_none=True)[0], "run", command],
189
+ [
190
+ *prerequisites.get_js_package_executor(raise_on_none=True)[0],
191
+ "run",
192
+ "export",
193
+ ],
216
194
  cwd=wdir,
217
195
  shell=constants.IS_WINDOWS,
196
+ env={
197
+ **os.environ,
198
+ "NO_COLOR": "1",
199
+ },
218
200
  )
219
201
  processes.show_progress("Creating Production Build", process, checkpoints)
202
+ _duplicate_index_html_to_parent_dir(wdir / constants.Dirs.STATIC)
220
203
 
221
204
 
222
205
  def setup_frontend(
223
206
  root: Path,
224
- disable_telemetry: bool = True,
225
207
  ):
226
208
  """Set up the frontend to run the app.
227
209
 
228
210
  Args:
229
211
  root: The root path of the project.
230
- disable_telemetry: Whether to disable the Next telemetry.
231
212
  """
232
- # Create the assets dir if it doesn't exist.
233
- path_ops.mkdir(constants.Dirs.APP_ASSETS)
234
- path_ops.copy_tree(
235
- src=str(root / constants.Dirs.APP_ASSETS),
236
- dest=str(root / prerequisites.get_web_dir() / constants.Dirs.PUBLIC),
237
- ignore=tuple(f"*.{ext}" for ext in constants.Reflex.STYLESHEETS_SUPPORTED),
238
- )
239
-
240
213
  # Set the environment variables in client (env.json).
241
214
  set_env_json()
242
215
 
243
216
  # update the last reflex run time.
244
217
  prerequisites.set_last_reflex_run_time()
245
218
 
246
- # Disable the Next telemetry.
247
- if disable_telemetry:
248
- processes.new_process(
249
- [
250
- *prerequisites.get_js_package_executor(raise_on_none=True)[0],
251
- "run",
252
- "next",
253
- "telemetry",
254
- "disable",
255
- ],
256
- cwd=prerequisites.get_web_dir(),
257
- stdout=subprocess.DEVNULL,
258
- shell=constants.IS_WINDOWS,
259
- )
260
-
261
219
 
262
220
  def setup_frontend_prod(
263
221
  root: Path,
264
- disable_telemetry: bool = True,
265
222
  ):
266
223
  """Set up the frontend for prod mode.
267
224
 
268
225
  Args:
269
226
  root: The root path of the project.
270
- disable_telemetry: Whether to disable the Next telemetry.
271
227
  """
272
- setup_frontend(root, disable_telemetry)
273
- build(deploy_url=get_config().deploy_url)
228
+ setup_frontend(root)
229
+ build()
274
230
 
275
231
 
276
232
  def _looks_like_venv_dir(dir_to_check: str | Path) -> bool:
reflex/utils/exec.py CHANGED
@@ -12,7 +12,7 @@ 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
+ from typing import Any, NamedTuple, TypedDict
16
16
  from urllib.parse import urljoin
17
17
 
18
18
  import psutil
@@ -170,7 +170,12 @@ def run_process_and_launch_url(
170
170
 
171
171
  while True:
172
172
  if process is None:
173
- kwargs = {}
173
+ kwargs: dict[str, Any] = {
174
+ "env": {
175
+ **os.environ,
176
+ "NO_COLOR": "1",
177
+ }
178
+ }
174
179
  if constants.IS_WINDOWS and backend_present:
175
180
  kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # pyright: ignore [reportAttributeAccessIssue]
176
181
  process = processes.new_process(
@@ -195,7 +200,7 @@ def run_process_and_launch_url(
195
200
  + format_change("Dev Dependencies", dev_dependencies_change)
196
201
  )
197
202
 
198
- match = re.search(constants.Next.FRONTEND_LISTENING_REGEX, line)
203
+ match = re.search(constants.ReactRouter.FRONTEND_LISTENING_REGEX, line)
199
204
  if match:
200
205
  if first_run:
201
206
  url = match.group(1)
@@ -367,21 +372,48 @@ def run_backend(
367
372
  run_uvicorn_backend(host, port, loglevel)
368
373
 
369
374
 
375
+ def _has_child_file(directory: Path, file_name: str) -> bool:
376
+ """Check if a directory has a child file with the given name.
377
+
378
+ Args:
379
+ directory: The directory to check.
380
+ file_name: The name of the file to look for.
381
+
382
+ Returns:
383
+ True if the directory has a child file with the given name, False otherwise.
384
+ """
385
+ return any(child_file.name == file_name for child_file in directory.iterdir())
386
+
387
+
370
388
  def get_reload_paths() -> Sequence[Path]:
371
389
  """Get the reload paths for the backend.
372
390
 
373
391
  Returns:
374
392
  The reload paths for the backend.
393
+
394
+ Raises:
395
+ RuntimeError: If the `__init__.py` file is found in the app root directory.
375
396
  """
376
397
  config = get_config()
377
398
  reload_paths = [Path.cwd()]
378
399
  if (spec := importlib.util.find_spec(config.module)) is not None and spec.origin:
379
400
  module_path = Path(spec.origin).resolve().parent
380
401
 
381
- while module_path.parent.name and any(
382
- sibling_file.name == "__init__.py" for sibling_file in module_path.iterdir()
383
- ):
384
- # go up a level to find dir without `__init__.py`
402
+ while module_path.parent.name and _has_child_file(module_path, "__init__.py"):
403
+ if _has_child_file(module_path, "rxconfig.py"):
404
+ init_file = module_path / "__init__.py"
405
+ init_file_content = init_file.read_text()
406
+ if init_file_content.strip():
407
+ msg = "There should not be an `__init__.py` file in your app root directory"
408
+ raise RuntimeError(msg)
409
+ console.warn(
410
+ "Removing `__init__.py` file in the app root directory. "
411
+ "This file can cause issues with module imports. "
412
+ )
413
+ init_file.unlink()
414
+ break
415
+
416
+ # go up a level to find dir without `__init__.py` or with `rxconfig.py`
385
417
  module_path = module_path.parent
386
418
 
387
419
  reload_paths = [module_path]
@@ -500,74 +532,6 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
500
532
  ).serve()
501
533
 
502
534
 
503
- def _deprecate_asgi_config(
504
- config_name: str,
505
- reason: str = "",
506
- ):
507
- console.deprecate(
508
- f"config.{config_name}",
509
- reason=reason,
510
- deprecation_version="0.7.9",
511
- removal_version="0.8.0",
512
- )
513
-
514
-
515
- @once
516
- def _get_backend_workers():
517
- from reflex.utils import processes
518
-
519
- config = get_config()
520
-
521
- gunicorn_workers = config.gunicorn_workers or 0
522
-
523
- if config.gunicorn_workers is not None:
524
- _deprecate_asgi_config(
525
- "gunicorn_workers",
526
- "If you're using Granian, use GRANIAN_WORKERS instead.",
527
- )
528
-
529
- return gunicorn_workers if gunicorn_workers else processes.get_num_workers()
530
-
531
-
532
- @once
533
- def _get_backend_timeout():
534
- config = get_config()
535
-
536
- timeout = config.timeout or 120
537
-
538
- if config.timeout is not None:
539
- _deprecate_asgi_config(
540
- "timeout",
541
- "If you're using Granian, use GRANIAN_WORKERS_LIFETIME instead.",
542
- )
543
-
544
- return timeout
545
-
546
-
547
- @once
548
- def _get_backend_max_requests():
549
- config = get_config()
550
-
551
- gunicorn_max_requests = config.gunicorn_max_requests or 120
552
-
553
- if config.gunicorn_max_requests is not None:
554
- _deprecate_asgi_config("gunicorn_max_requests")
555
-
556
- return gunicorn_max_requests
557
-
558
-
559
- @once
560
- def _get_backend_max_requests_jitter():
561
- config = get_config()
562
-
563
- gunicorn_max_requests_jitter = config.gunicorn_max_requests_jitter or 25
564
-
565
- if config.gunicorn_max_requests_jitter is not None:
566
- _deprecate_asgi_config("gunicorn_max_requests_jitter")
567
-
568
- return gunicorn_max_requests_jitter
569
-
570
-
571
535
  def run_backend_prod(
572
536
  host: str,
573
537
  port: int,
@@ -601,72 +565,12 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
601
565
  """
602
566
  from reflex.utils import processes
603
567
 
604
- config = get_config()
605
-
606
568
  app_module = get_app_instance()
607
569
 
608
570
  command = (
609
- [
610
- "uvicorn",
611
- *(
612
- (
613
- "--limit-max-requests",
614
- str(max_requessts),
615
- )
616
- if (
617
- (max_requessts := _get_backend_max_requests()) is not None
618
- and max_requessts > 0
619
- )
620
- else ()
621
- ),
622
- *(
623
- ("--timeout-keep-alive", str(timeout))
624
- if (timeout := _get_backend_timeout()) is not None
625
- else ()
626
- ),
627
- *("--host", host),
628
- *("--port", str(port)),
629
- *("--workers", str(_get_backend_workers())),
630
- "--factory",
631
- app_module,
632
- ]
571
+ ["uvicorn", *("--host", host), *("--port", str(port)), "--factory", app_module]
633
572
  if constants.IS_WINDOWS
634
- else [
635
- "gunicorn",
636
- *("--worker-class", config.gunicorn_worker_class),
637
- *(
638
- (
639
- "--max-requests",
640
- str(max_requessts),
641
- )
642
- if (
643
- (max_requessts := _get_backend_max_requests()) is not None
644
- and max_requessts > 0
645
- )
646
- else ()
647
- ),
648
- *(
649
- (
650
- "--max-requests-jitter",
651
- str(max_requessts_jitter),
652
- )
653
- if (
654
- (max_requessts_jitter := _get_backend_max_requests_jitter())
655
- is not None
656
- and max_requessts_jitter > 0
657
- )
658
- else ()
659
- ),
660
- "--preload",
661
- *(
662
- ("--timeout", str(timeout))
663
- if (timeout := _get_backend_timeout()) is not None
664
- else ()
665
- ),
666
- *("--bind", f"{host}:{port}"),
667
- *("--threads", str(_get_backend_workers())),
668
- f"{app_module}()",
669
- ]
573
+ else ["gunicorn", "--preload", *("--bind", f"{host}:{port}"), f"{app_module}()"]
670
574
  )
671
575
 
672
576
  command += [
@@ -691,32 +595,26 @@ def run_granian_backend_prod(host: str, port: int, loglevel: LogLevel):
691
595
  port: The app port
692
596
  loglevel: The log level.
693
597
  """
598
+ from granian.constants import Interfaces
599
+
694
600
  from reflex.utils import processes
695
601
 
696
- try:
697
- from granian.constants import Interfaces
698
-
699
- command = [
700
- "granian",
701
- *("--workers", str(_get_backend_workers())),
702
- *("--log-level", "critical"),
703
- *("--host", host),
704
- *("--port", str(port)),
705
- *("--interface", str(Interfaces.ASGI)),
706
- *("--factory", get_app_instance_from_file()),
707
- ]
708
- processes.new_process(
709
- command,
710
- run=True,
711
- show_logs=True,
712
- env={
713
- environment.REFLEX_SKIP_COMPILE.name: "true"
714
- }, # skip compile for prod backend
715
- )
716
- except ImportError:
717
- console.error(
718
- 'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian[reload]>=1.6.0"`)'
719
- )
602
+ command = [
603
+ "granian",
604
+ *("--log-level", "critical"),
605
+ *("--host", host),
606
+ *("--port", str(port)),
607
+ *("--interface", str(Interfaces.ASGI)),
608
+ *("--factory", get_app_instance_from_file()),
609
+ ]
610
+ processes.new_process(
611
+ command,
612
+ run=True,
613
+ show_logs=True,
614
+ env={
615
+ environment.REFLEX_SKIP_COMPILE.name: "true"
616
+ }, # skip compile for prod backend
617
+ )
720
618
 
721
619
 
722
620
  def output_system_info():
reflex/utils/export.py CHANGED
@@ -56,13 +56,13 @@ def export(
56
56
 
57
57
  if frontend:
58
58
  # Ensure module can be imported and app.compile() is called.
59
- prerequisites.get_compiled_app(export=True)
59
+ prerequisites.get_compiled_app(prerender_routes=True)
60
60
  # Set up .web directory and install frontend dependencies.
61
61
  build.setup_frontend(Path.cwd())
62
62
 
63
63
  # Build the static app.
64
64
  if frontend:
65
- build.build(deploy_url=config.deploy_url, for_export=True)
65
+ build.build()
66
66
 
67
67
  # Zip up the app.
68
68
  if zipping:
reflex/utils/imports.py CHANGED
@@ -114,10 +114,6 @@ class ImportVar:
114
114
  # The path of the package to import from.
115
115
  package_path: str = "/"
116
116
 
117
- # whether this import package should be added to transpilePackages in next.config.js
118
- # https://nextjs.org/docs/app/api-reference/next-config-js/transpilePackages
119
- transpile: bool | None = False
120
-
121
117
  @property
122
118
  def name(self) -> str:
123
119
  """The name of the import.
reflex/utils/misc.py CHANGED
@@ -1,7 +1,11 @@
1
1
  """Miscellaneous functions for the experimental package."""
2
2
 
3
3
  import asyncio
4
+ import contextlib
5
+ import sys
6
+ import threading
4
7
  from collections.abc import Callable
8
+ from pathlib import Path
5
9
  from typing import Any
6
10
 
7
11
 
@@ -23,3 +27,27 @@ async def run_in_thread(func: Callable) -> Any:
23
27
  msg = "func must be a non-async function"
24
28
  raise ValueError(msg)
25
29
  return await asyncio.get_event_loop().run_in_executor(None, func)
30
+
31
+
32
+ # Global lock for thread-safe sys.path manipulation
33
+ _sys_path_lock = threading.RLock()
34
+
35
+
36
+ @contextlib.contextmanager
37
+ def with_cwd_in_syspath():
38
+ """Temporarily add current working directory to sys.path in a thread-safe manner.
39
+
40
+ This context manager temporarily prepends the current working directory to sys.path,
41
+ ensuring that modules in the current directory can be imported. The original sys.path
42
+ is restored when exiting the context.
43
+
44
+ Yields:
45
+ None
46
+ """
47
+ with _sys_path_lock:
48
+ orig_sys_path = sys.path.copy()
49
+ sys.path.insert(0, str(Path.cwd()))
50
+ try:
51
+ yield
52
+ finally:
53
+ sys.path[:] = orig_sys_path