reflex 0.7.14a5__py3-none-any.whl → 0.8.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 (236) 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 +21 -11
  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/styles/styles.css.jinja2 +1 -0
  8. reflex/.templates/jinja/web/utils/context.js.jinja2 +25 -8
  9. reflex/.templates/web/app/entry.client.js +8 -0
  10. reflex/.templates/web/app/routes.js +10 -0
  11. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +12 -37
  12. reflex/.templates/web/postcss.config.js +1 -1
  13. reflex/.templates/web/react-router.config.js +6 -0
  14. reflex/.templates/web/styles/__reflex_style_reset.css +399 -0
  15. reflex/.templates/web/utils/client_side_routing.js +21 -19
  16. reflex/.templates/web/utils/react-theme.js +92 -0
  17. reflex/.templates/web/utils/state.js +251 -100
  18. reflex/.templates/web/vite-plugin-safari-cachebust.js +160 -0
  19. reflex/.templates/web/vite.config.js +39 -0
  20. reflex/__init__.py +1 -6
  21. reflex/__init__.pyi +327 -192
  22. reflex/app.py +103 -152
  23. reflex/base.py +1 -87
  24. reflex/compiler/compiler.py +70 -19
  25. reflex/compiler/templates.py +3 -3
  26. reflex/compiler/utils.py +91 -33
  27. reflex/components/__init__.py +0 -2
  28. reflex/components/__init__.pyi +34 -18
  29. reflex/components/base/__init__.py +1 -5
  30. reflex/components/base/__init__.pyi +30 -21
  31. reflex/components/base/app_wrap.pyi +7 -7
  32. reflex/components/base/body.pyi +7 -7
  33. reflex/components/base/document.py +18 -14
  34. reflex/components/base/document.pyi +88 -38
  35. reflex/components/base/error_boundary.pyi +7 -7
  36. reflex/components/base/fragment.pyi +7 -7
  37. reflex/components/base/link.pyi +12 -12
  38. reflex/components/base/meta.py +4 -15
  39. reflex/components/base/meta.pyi +31 -31
  40. reflex/components/base/script.py +60 -58
  41. reflex/components/base/script.pyi +248 -34
  42. reflex/components/base/strict_mode.pyi +7 -7
  43. reflex/components/component.py +146 -217
  44. reflex/components/core/__init__.py +1 -0
  45. reflex/components/core/__init__.pyi +77 -37
  46. reflex/components/core/auto_scroll.pyi +7 -7
  47. reflex/components/core/banner.pyi +33 -33
  48. reflex/components/core/client_side_routing.py +7 -6
  49. reflex/components/core/client_side_routing.pyi +8 -59
  50. reflex/components/core/clipboard.pyi +7 -7
  51. reflex/components/core/debounce.py +1 -0
  52. reflex/components/core/debounce.pyi +7 -7
  53. reflex/components/core/foreach.py +5 -4
  54. reflex/components/core/helmet.py +14 -0
  55. reflex/components/{next/base.pyi → core/helmet.pyi} +12 -10
  56. reflex/components/core/html.pyi +7 -7
  57. reflex/components/core/match.py +3 -3
  58. reflex/components/core/sticky.pyi +21 -20
  59. reflex/components/core/upload.py +4 -2
  60. reflex/components/core/upload.pyi +26 -25
  61. reflex/components/datadisplay/__init__.pyi +13 -7
  62. reflex/components/datadisplay/code.py +14 -79
  63. reflex/components/datadisplay/code.pyi +11 -13
  64. reflex/components/datadisplay/dataeditor.pyi +38 -15
  65. reflex/components/datadisplay/shiki_code_block.py +5 -3
  66. reflex/components/datadisplay/shiki_code_block.pyi +16 -15
  67. reflex/components/dynamic.py +5 -5
  68. reflex/components/el/__init__.pyi +506 -246
  69. reflex/components/el/element.pyi +7 -7
  70. reflex/components/el/elements/__init__.pyi +504 -245
  71. reflex/components/el/elements/base.pyi +7 -7
  72. reflex/components/el/elements/forms.pyi +146 -101
  73. reflex/components/el/elements/inline.pyi +142 -142
  74. reflex/components/el/elements/media.pyi +131 -130
  75. reflex/components/el/elements/metadata.pyi +32 -32
  76. reflex/components/el/elements/other.pyi +37 -37
  77. reflex/components/el/elements/scripts.pyi +17 -17
  78. reflex/components/el/elements/sectioning.pyi +77 -77
  79. reflex/components/el/elements/tables.pyi +52 -52
  80. reflex/components/el/elements/typography.pyi +77 -77
  81. reflex/components/field.py +175 -0
  82. reflex/components/gridjs/datatable.py +2 -2
  83. reflex/components/gridjs/datatable.pyi +14 -14
  84. reflex/components/lucide/icon.py +6 -2
  85. reflex/components/lucide/icon.pyi +19 -17
  86. reflex/components/markdown/markdown.py +5 -3
  87. reflex/components/markdown/markdown.pyi +7 -7
  88. reflex/components/moment/moment.py +1 -1
  89. reflex/components/moment/moment.pyi +7 -7
  90. reflex/components/plotly/plotly.py +12 -6
  91. reflex/components/plotly/plotly.pyi +50 -49
  92. reflex/components/props.py +376 -27
  93. reflex/components/radix/__init__.pyi +123 -65
  94. reflex/components/radix/primitives/__init__.pyi +6 -4
  95. reflex/components/radix/primitives/accordion.py +8 -1
  96. reflex/components/radix/primitives/accordion.pyi +37 -37
  97. reflex/components/radix/primitives/base.pyi +12 -12
  98. reflex/components/radix/primitives/drawer.pyi +56 -55
  99. reflex/components/radix/primitives/form.pyi +63 -53
  100. reflex/components/radix/primitives/progress.pyi +26 -25
  101. reflex/components/radix/primitives/slider.pyi +27 -27
  102. reflex/components/radix/themes/__init__.pyi +5 -6
  103. reflex/components/radix/themes/base.py +3 -3
  104. reflex/components/radix/themes/base.pyi +42 -42
  105. reflex/components/radix/themes/color_mode.py +5 -6
  106. reflex/components/radix/themes/color_mode.pyi +17 -17
  107. reflex/components/radix/themes/components/__init__.pyi +75 -38
  108. reflex/components/radix/themes/components/alert_dialog.pyi +37 -37
  109. reflex/components/radix/themes/components/aspect_ratio.pyi +7 -7
  110. reflex/components/radix/themes/components/avatar.pyi +7 -7
  111. reflex/components/radix/themes/components/badge.pyi +7 -7
  112. reflex/components/radix/themes/components/button.pyi +7 -7
  113. reflex/components/radix/themes/components/callout.pyi +26 -25
  114. reflex/components/radix/themes/components/card.pyi +7 -7
  115. reflex/components/radix/themes/components/checkbox.pyi +16 -15
  116. reflex/components/radix/themes/components/checkbox_cards.pyi +12 -12
  117. reflex/components/radix/themes/components/checkbox_group.pyi +12 -12
  118. reflex/components/radix/themes/components/context_menu.pyi +67 -67
  119. reflex/components/radix/themes/components/data_list.pyi +22 -22
  120. reflex/components/radix/themes/components/dialog.pyi +36 -35
  121. reflex/components/radix/themes/components/dropdown_menu.pyi +42 -42
  122. reflex/components/radix/themes/components/hover_card.pyi +21 -20
  123. reflex/components/radix/themes/components/icon_button.pyi +7 -7
  124. reflex/components/radix/themes/components/inset.pyi +7 -7
  125. reflex/components/radix/themes/components/popover.pyi +22 -22
  126. reflex/components/radix/themes/components/progress.pyi +7 -7
  127. reflex/components/radix/themes/components/radio.pyi +7 -7
  128. reflex/components/radix/themes/components/radio_cards.pyi +12 -12
  129. reflex/components/radix/themes/components/radio_group.pyi +21 -20
  130. reflex/components/radix/themes/components/scroll_area.pyi +7 -7
  131. reflex/components/radix/themes/components/segmented_control.pyi +12 -12
  132. reflex/components/radix/themes/components/select.pyi +46 -45
  133. reflex/components/radix/themes/components/separator.pyi +7 -7
  134. reflex/components/radix/themes/components/skeleton.pyi +7 -7
  135. reflex/components/radix/themes/components/slider.pyi +17 -9
  136. reflex/components/radix/themes/components/spinner.pyi +7 -7
  137. reflex/components/radix/themes/components/switch.pyi +7 -7
  138. reflex/components/radix/themes/components/table.pyi +37 -37
  139. reflex/components/radix/themes/components/tabs.pyi +26 -25
  140. reflex/components/radix/themes/components/text_area.pyi +15 -9
  141. reflex/components/radix/themes/components/text_field.pyi +32 -19
  142. reflex/components/radix/themes/components/tooltip.pyi +7 -7
  143. reflex/components/radix/themes/layout/__init__.pyi +27 -14
  144. reflex/components/radix/themes/layout/base.pyi +7 -7
  145. reflex/components/radix/themes/layout/box.pyi +7 -7
  146. reflex/components/radix/themes/layout/center.pyi +7 -7
  147. reflex/components/radix/themes/layout/container.pyi +7 -7
  148. reflex/components/radix/themes/layout/flex.pyi +7 -7
  149. reflex/components/radix/themes/layout/grid.pyi +7 -7
  150. reflex/components/radix/themes/layout/list.pyi +26 -25
  151. reflex/components/radix/themes/layout/section.pyi +7 -7
  152. reflex/components/radix/themes/layout/spacer.pyi +7 -7
  153. reflex/components/radix/themes/layout/stack.pyi +17 -17
  154. reflex/components/radix/themes/typography/__init__.pyi +7 -5
  155. reflex/components/radix/themes/typography/blockquote.pyi +7 -7
  156. reflex/components/radix/themes/typography/code.pyi +7 -7
  157. reflex/components/radix/themes/typography/heading.pyi +7 -7
  158. reflex/components/radix/themes/typography/link.py +46 -11
  159. reflex/components/radix/themes/typography/link.pyi +312 -9
  160. reflex/components/radix/themes/typography/text.pyi +36 -35
  161. reflex/components/react_player/audio.pyi +10 -8
  162. reflex/components/react_player/react_player.pyi +7 -7
  163. reflex/components/react_player/video.pyi +10 -8
  164. reflex/components/recharts/__init__.pyi +208 -100
  165. reflex/components/recharts/cartesian.py +10 -8
  166. reflex/components/recharts/cartesian.pyi +90 -94
  167. reflex/components/recharts/charts.py +4 -2
  168. reflex/components/recharts/charts.pyi +49 -49
  169. reflex/components/recharts/general.pyi +31 -31
  170. reflex/components/recharts/polar.py +8 -4
  171. reflex/components/recharts/polar.pyi +23 -23
  172. reflex/components/recharts/recharts.py +2 -2
  173. reflex/components/recharts/recharts.pyi +12 -12
  174. reflex/components/sonner/toast.py +3 -3
  175. reflex/components/sonner/toast.pyi +9 -9
  176. reflex/config.py +10 -113
  177. reflex/constants/__init__.py +2 -2
  178. reflex/constants/base.py +28 -11
  179. reflex/constants/compiler.py +12 -3
  180. reflex/constants/event.py +1 -0
  181. reflex/constants/installer.py +26 -20
  182. reflex/constants/route.py +27 -8
  183. reflex/constants/state.py +2 -0
  184. reflex/custom_components/custom_components.py +0 -14
  185. reflex/environment.py +77 -5
  186. reflex/event.py +178 -81
  187. reflex/experimental/__init__.py +0 -30
  188. reflex/istate/__init__.py +69 -0
  189. reflex/istate/manager.py +1 -0
  190. reflex/istate/proxy.py +5 -3
  191. reflex/page.py +0 -27
  192. reflex/plugins/__init__.py +3 -2
  193. reflex/plugins/base.py +5 -1
  194. reflex/plugins/shared_tailwind.py +215 -0
  195. reflex/plugins/sitemap.py +206 -0
  196. reflex/plugins/tailwind_v3.py +15 -108
  197. reflex/plugins/tailwind_v4.py +18 -110
  198. reflex/reflex.py +1 -0
  199. reflex/route.py +157 -75
  200. reflex/state.py +171 -155
  201. reflex/testing.py +86 -16
  202. reflex/utils/build.py +38 -82
  203. reflex/utils/exec.py +83 -175
  204. reflex/utils/export.py +2 -2
  205. reflex/utils/format.py +1 -5
  206. reflex/utils/imports.py +5 -16
  207. reflex/utils/misc.py +67 -0
  208. reflex/utils/prerequisites.py +66 -68
  209. reflex/utils/processes.py +24 -47
  210. reflex/utils/pyi_generator.py +44 -49
  211. reflex/utils/serializers.py +14 -1
  212. reflex/utils/telemetry.py +0 -15
  213. reflex/utils/types.py +197 -62
  214. reflex/vars/__init__.py +2 -0
  215. reflex/vars/base.py +367 -134
  216. {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/METADATA +15 -8
  217. reflex-0.8.0.dist-info/RECORD +403 -0
  218. reflex/.templates/web/next.config.js +0 -7
  219. reflex/components/base/head.py +0 -20
  220. reflex/components/base/head.pyi +0 -116
  221. reflex/components/next/__init__.py +0 -10
  222. reflex/components/next/base.py +0 -7
  223. reflex/components/next/image.py +0 -117
  224. reflex/components/next/image.pyi +0 -94
  225. reflex/components/next/link.py +0 -20
  226. reflex/components/next/link.pyi +0 -67
  227. reflex/components/next/video.py +0 -38
  228. reflex/components/next/video.pyi +0 -68
  229. reflex/components/suneditor/__init__.py +0 -5
  230. reflex/components/suneditor/editor.py +0 -269
  231. reflex/components/suneditor/editor.pyi +0 -199
  232. reflex/experimental/layout.py +0 -254
  233. reflex-0.7.14a5.dist-info/RECORD +0 -407
  234. {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/WHEEL +0 -0
  235. {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/entry_points.txt +0 -0
  236. {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/licenses/LICENSE +0 -0
reflex/utils/exec.py CHANGED
@@ -12,17 +12,16 @@ 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
- import psutil
19
-
20
18
  from reflex import constants
21
19
  from reflex.config import get_config
22
20
  from reflex.constants.base import LogLevel
23
21
  from reflex.environment import environment
24
22
  from reflex.utils import console, path_ops
25
23
  from reflex.utils.decorator import once
24
+ from reflex.utils.misc import get_module_path
26
25
  from reflex.utils.prerequisites import get_web_dir
27
26
 
28
27
  # For uvicorn windows bug fix (#2335)
@@ -130,12 +129,16 @@ def get_different_packages(
130
129
  def kill(proc_pid: int):
131
130
  """Kills a process and all its child processes.
132
131
 
132
+ Requires the `psutil` library to be installed.
133
+
133
134
  Args:
134
- proc_pid (int): The process ID of the process to be killed.
135
+ proc_pid: The process ID of the process to be killed.
135
136
 
136
137
  Example:
137
138
  >>> kill(1234)
138
139
  """
140
+ import psutil
141
+
139
142
  process = psutil.Process(proc_pid)
140
143
  for proc in process.children(recursive=True):
141
144
  proc.kill()
@@ -170,7 +173,12 @@ def run_process_and_launch_url(
170
173
 
171
174
  while True:
172
175
  if process is None:
173
- kwargs = {}
176
+ kwargs: dict[str, Any] = {
177
+ "env": {
178
+ **os.environ,
179
+ "NO_COLOR": "1",
180
+ }
181
+ }
174
182
  if constants.IS_WINDOWS and backend_present:
175
183
  kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # pyright: ignore [reportAttributeAccessIssue]
176
184
  process = processes.new_process(
@@ -195,7 +203,7 @@ def run_process_and_launch_url(
195
203
  + format_change("Dev Dependencies", dev_dependencies_change)
196
204
  )
197
205
 
198
- match = re.search(constants.Next.FRONTEND_LISTENING_REGEX, line)
206
+ match = re.search(constants.ReactRouter.FRONTEND_LISTENING_REGEX, line)
199
207
  if match:
200
208
  if first_run:
201
209
  url = match.group(1)
@@ -318,15 +326,12 @@ def get_app_file() -> Path:
318
326
  if current_working_dir not in sys.path:
319
327
  # Add the current working directory to sys.path
320
328
  sys.path.insert(0, current_working_dir)
321
- module_spec = importlib.util.find_spec(get_app_module())
322
- if module_spec is None:
323
- msg = f"Module {get_app_module()} not found. Make sure the module is installed."
329
+ app_module = get_app_module()
330
+ module_path = get_module_path(app_module)
331
+ if module_path is None:
332
+ msg = f"Module {app_module} not found. Make sure the module is installed."
324
333
  raise ImportError(msg)
325
- file_name = module_spec.origin
326
- if file_name is None:
327
- msg = f"Module {get_app_module()} not found. Make sure the module is installed."
328
- raise ImportError(msg)
329
- return Path(file_name).resolve()
334
+ return module_path
330
335
 
331
336
 
332
337
  def get_app_instance_from_file() -> str:
@@ -362,26 +367,58 @@ def run_backend(
362
367
 
363
368
  # Run the backend in development mode.
364
369
  if should_use_granian():
370
+ # We import reflex app because this lets granian cache the module
371
+ import reflex.app # noqa: F401
372
+
365
373
  run_granian_backend(host, port, loglevel)
366
374
  else:
367
375
  run_uvicorn_backend(host, port, loglevel)
368
376
 
369
377
 
378
+ def _has_child_file(directory: Path, file_name: str) -> bool:
379
+ """Check if a directory has a child file with the given name.
380
+
381
+ Args:
382
+ directory: The directory to check.
383
+ file_name: The name of the file to look for.
384
+
385
+ Returns:
386
+ True if the directory has a child file with the given name, False otherwise.
387
+ """
388
+ return any(child_file.name == file_name for child_file in directory.iterdir())
389
+
390
+
370
391
  def get_reload_paths() -> Sequence[Path]:
371
392
  """Get the reload paths for the backend.
372
393
 
373
394
  Returns:
374
395
  The reload paths for the backend.
396
+
397
+ Raises:
398
+ RuntimeError: If the `__init__.py` file is found in the app root directory.
375
399
  """
376
400
  config = get_config()
377
401
  reload_paths = [Path.cwd()]
378
- if (spec := importlib.util.find_spec(config.module)) is not None and spec.origin:
379
- module_path = Path(spec.origin).resolve().parent
402
+ app_module = config.module
403
+ module_path = get_module_path(app_module)
404
+ if module_path is not None:
405
+ module_path = module_path.parent
406
+
407
+ while module_path.parent.name and _has_child_file(module_path, "__init__.py"):
408
+ if _has_child_file(module_path, "rxconfig.py"):
409
+ init_file = module_path / "__init__.py"
410
+ init_file_content = init_file.read_text()
411
+ if init_file_content.strip():
412
+ msg = "There should not be an `__init__.py` file in your app root directory"
413
+ raise RuntimeError(msg)
414
+ console.warn(
415
+ "Removing `__init__.py` file in the app root directory. "
416
+ "This file can cause issues with module imports. "
417
+ )
418
+ init_file.unlink()
419
+ break
380
420
 
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`
421
+ # go up a level to find dir without `__init__.py` or with `rxconfig.py`
385
422
  module_path = module_path.parent
386
423
 
387
424
  reload_paths = [module_path]
@@ -482,9 +519,11 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
482
519
 
483
520
  from granian.constants import Interfaces
484
521
  from granian.log import LogLevels
485
- from granian.server import MPServer as Granian
522
+ from granian.server import Server as Granian
486
523
 
487
- Granian(
524
+ from reflex.environment import _paths_from_environment
525
+
526
+ granian_app = Granian(
488
527
  target=get_app_instance_from_file(),
489
528
  factory=True,
490
529
  address=host,
@@ -496,76 +535,11 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
496
535
  reload_ignore_worker_failure=True,
497
536
  reload_ignore_patterns=HOTRELOAD_IGNORE_PATTERNS,
498
537
  reload_tick=100,
538
+ env_files=_paths_from_environment() or None,
499
539
  workers_kill_timeout=2,
500
- ).serve()
501
-
502
-
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
540
  )
513
541
 
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
542
+ granian_app.serve()
569
543
 
570
544
 
571
545
  def run_backend_prod(
@@ -601,72 +575,12 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
601
575
  """
602
576
  from reflex.utils import processes
603
577
 
604
- config = get_config()
605
-
606
578
  app_module = get_app_instance()
607
579
 
608
580
  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
- ]
581
+ ["uvicorn", *("--host", host), *("--port", str(port)), "--factory", app_module]
633
582
  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
- ]
583
+ else ["gunicorn", "--preload", *("--bind", f"{host}:{port}"), f"{app_module}()"]
670
584
  )
671
585
 
672
586
  command += [
@@ -691,32 +605,26 @@ def run_granian_backend_prod(host: str, port: int, loglevel: LogLevel):
691
605
  port: The app port
692
606
  loglevel: The log level.
693
607
  """
608
+ from granian.constants import Interfaces
609
+
694
610
  from reflex.utils import processes
695
611
 
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
- )
612
+ command = [
613
+ "granian",
614
+ *("--log-level", "critical"),
615
+ *("--host", host),
616
+ *("--port", str(port)),
617
+ *("--interface", str(Interfaces.ASGI)),
618
+ *("--factory", get_app_instance_from_file()),
619
+ ]
620
+ processes.new_process(
621
+ command,
622
+ run=True,
623
+ show_logs=True,
624
+ env={
625
+ environment.REFLEX_SKIP_COMPILE.name: "true"
626
+ }, # skip compile for prod backend
627
+ )
720
628
 
721
629
 
722
630
  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/format.py CHANGED
@@ -310,20 +310,16 @@ def format_var(var: Var) -> str:
310
310
  return str(var)
311
311
 
312
312
 
313
- def format_route(route: str, format_case: bool = True) -> str:
313
+ def format_route(route: str) -> str:
314
314
  """Format the given route.
315
315
 
316
316
  Args:
317
317
  route: The route to format.
318
- format_case: whether to format case to kebab case.
319
318
 
320
319
  Returns:
321
320
  The formatted route.
322
321
  """
323
322
  route = route.strip("/")
324
- # Strip the route and format casing.
325
- if format_case:
326
- route = to_kebab_case(route)
327
323
 
328
324
  # If the route is empty, return the index route.
329
325
  if route == "":
reflex/utils/imports.py CHANGED
@@ -52,19 +52,12 @@ def parse_imports(
52
52
  Returns:
53
53
  The parsed import dict.
54
54
  """
55
-
56
- def _make_list(
57
- value: ImmutableImportTypes,
58
- ) -> list[str | ImportVar] | list[ImportVar]:
59
- if isinstance(value, (str, ImportVar)):
60
- return [value]
61
- return list(value)
62
-
63
55
  return {
64
- package: [
65
- ImportVar(tag=tag) if isinstance(tag, str) else tag
66
- for tag in _make_list(maybe_tags)
67
- ]
56
+ package: [maybe_tags]
57
+ if isinstance(maybe_tags, ImportVar)
58
+ else [ImportVar(tag=maybe_tags)]
59
+ if isinstance(maybe_tags, str)
60
+ else [ImportVar(tag=tag) if isinstance(tag, str) else tag for tag in maybe_tags]
68
61
  for package, maybe_tags in imports.items()
69
62
  }
70
63
 
@@ -114,10 +107,6 @@ class ImportVar:
114
107
  # The path of the package to import from.
115
108
  package_path: str = "/"
116
109
 
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
110
  @property
122
111
  def name(self) -> str:
123
112
  """The name of the import.
reflex/utils/misc.py CHANGED
@@ -1,10 +1,53 @@
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
 
12
+ def get_module_path(module_name: str) -> Path | None:
13
+ """Check if a module exists and return its path.
14
+
15
+ This function searches for a module by navigating through the module hierarchy
16
+ in each path of sys.path, checking for both .py files and packages with __init__.py.
17
+
18
+ Args:
19
+ module_name: The name of the module to search for (e.g., "package.submodule").
20
+
21
+ Returns:
22
+ The path to the module file if found, None otherwise.
23
+ """
24
+ parts = module_name.split(".")
25
+
26
+ # Check each path in sys.path
27
+ for path in sys.path:
28
+ current_path = Path(path)
29
+
30
+ # Navigate through the module hierarchy
31
+ for i, part in enumerate(parts):
32
+ potential_file = current_path / (part + ".py")
33
+ potential_dir = current_path / part
34
+
35
+ if potential_file.is_file():
36
+ # We encountered a file, but we can't continue deeper
37
+ if i == len(parts) - 1:
38
+ return potential_file
39
+ return None # Can't continue deeper
40
+ if potential_dir.is_dir():
41
+ # It's a package, so we can continue deeper
42
+ current_path = potential_dir
43
+ else:
44
+ break # Path doesn't exist, break out of the loop
45
+ else:
46
+ return current_path / "__init__.py" # Made it through all parts
47
+
48
+ return None
49
+
50
+
8
51
  async def run_in_thread(func: Callable) -> Any:
9
52
  """Run a function in a separate thread.
10
53
 
@@ -23,3 +66,27 @@ async def run_in_thread(func: Callable) -> Any:
23
66
  msg = "func must be a non-async function"
24
67
  raise ValueError(msg)
25
68
  return await asyncio.get_event_loop().run_in_executor(None, func)
69
+
70
+
71
+ # Global lock for thread-safe sys.path manipulation
72
+ _sys_path_lock = threading.RLock()
73
+
74
+
75
+ @contextlib.contextmanager
76
+ def with_cwd_in_syspath():
77
+ """Temporarily add current working directory to sys.path in a thread-safe manner.
78
+
79
+ This context manager temporarily prepends the current working directory to sys.path,
80
+ ensuring that modules in the current directory can be imported. The original sys.path
81
+ is restored when exiting the context.
82
+
83
+ Yields:
84
+ None
85
+ """
86
+ with _sys_path_lock:
87
+ orig_sys_path = sys.path.copy()
88
+ sys.path.insert(0, str(Path.cwd()))
89
+ try:
90
+ yield
91
+ finally:
92
+ sys.path[:] = orig_sys_path