reflex 0.7.14a6__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 (237) 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 +86 -135
  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.14a6.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/experimental/layout.pyi +0 -814
  234. reflex-0.7.14a6.dist-info/RECORD +0 -408
  235. {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/WHEEL +0 -0
  236. {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/entry_points.txt +0 -0
  237. {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -39,9 +39,8 @@ from reflex.compiler import templates
39
39
  from reflex.config import Config, get_config
40
40
  from reflex.environment import environment
41
41
  from reflex.utils import console, net, path_ops, processes, redir
42
- from reflex.utils.decorator import once
43
42
  from reflex.utils.exceptions import SystemPackageMissingError
44
- from reflex.utils.format import format_library_name
43
+ from reflex.utils.misc import get_module_path
45
44
  from reflex.utils.registry import get_npm_registry
46
45
 
47
46
  if typing.TYPE_CHECKING:
@@ -74,7 +73,7 @@ class CpuInfo:
74
73
 
75
74
 
76
75
  def get_web_dir() -> Path:
77
- """Get the working directory for the next.js commands.
76
+ """Get the working directory for the frontend.
78
77
 
79
78
  Can be overridden with REFLEX_WEB_WORKDIR.
80
79
 
@@ -117,7 +116,7 @@ def check_latest_package_version(package_name: str):
117
116
  # Get the latest version from PyPI
118
117
  current_version = importlib.metadata.version(package_name)
119
118
  url = f"https://pypi.org/pypi/{package_name}/json"
120
- response = net.get(url)
119
+ response = net.get(url, timeout=2)
121
120
  latest_version = response.json()["info"]["version"]
122
121
  console.debug(f"Latest version of {package_name}: {latest_version}")
123
122
  if get_or_set_last_reflex_version_check_datetime():
@@ -334,13 +333,14 @@ def npm_escape_hatch() -> bool:
334
333
 
335
334
 
336
335
  def _check_app_name(config: Config):
337
- """Check if the app name is set in the config.
336
+ """Check if the app name is valid and matches the folder structure.
338
337
 
339
338
  Args:
340
339
  config: The config object.
341
340
 
342
341
  Raises:
343
- RuntimeError: If the app name is not set in the config.
342
+ RuntimeError: If the app name is not set, folder doesn't exist, or doesn't match config.
343
+ ModuleNotFoundError: If the app_name is not importable (i.e., not a valid Python package, folder structure being wrong).
344
344
  """
345
345
  if not config.app_name:
346
346
  msg = (
@@ -349,6 +349,18 @@ def _check_app_name(config: Config):
349
349
  )
350
350
  raise RuntimeError(msg)
351
351
 
352
+ from reflex.utils.misc import get_module_path, with_cwd_in_syspath
353
+
354
+ with with_cwd_in_syspath():
355
+ module_path = get_module_path(config.module)
356
+ if module_path is None:
357
+ msg = f"Module {config.module} not found. "
358
+ if config.app_module_import is not None:
359
+ msg += f"Ensure app_module_import='{config.app_module_import}' in rxconfig.py matches your folder structure."
360
+ else:
361
+ msg += f"Ensure app_name='{config.app_name}' in rxconfig.py matches your folder structure."
362
+ raise ModuleNotFoundError(msg)
363
+
352
364
 
353
365
  def get_app(reload: bool = False) -> ModuleType:
354
366
  """Get the app module based on the default config.
@@ -439,7 +451,7 @@ def validate_app(
439
451
 
440
452
  def get_compiled_app(
441
453
  reload: bool = False,
442
- export: bool = False,
454
+ prerender_routes: bool = False,
443
455
  dry_run: bool = False,
444
456
  check_if_schema_up_to_date: bool = False,
445
457
  ) -> ModuleType:
@@ -447,7 +459,7 @@ def get_compiled_app(
447
459
 
448
460
  Args:
449
461
  reload: Re-import the app module from disk
450
- export: Compile the app for export
462
+ prerender_routes: Whether to prerender routes.
451
463
  dry_run: If True, do not write the compiled app to disk.
452
464
  check_if_schema_up_to_date: If True, check if the schema is up to date.
453
465
 
@@ -460,13 +472,13 @@ def get_compiled_app(
460
472
  # For py3.9 compatibility when redis is used, we MUST add any decorator pages
461
473
  # before compiling the app in a thread to avoid event loop error (REF-2172).
462
474
  app._apply_decorated_pages()
463
- app._compile(export=export, dry_run=dry_run)
475
+ app._compile(prerender_routes=prerender_routes, dry_run=dry_run)
464
476
  return app_module
465
477
 
466
478
 
467
479
  def compile_app(
468
480
  reload: bool = False,
469
- export: bool = False,
481
+ prerender_routes: bool = False,
470
482
  dry_run: bool = False,
471
483
  check_if_schema_up_to_date: bool = False,
472
484
  ) -> None:
@@ -474,13 +486,13 @@ def compile_app(
474
486
 
475
487
  Args:
476
488
  reload: Re-import the app module from disk
477
- export: Compile the app for export
489
+ prerender_routes: Whether to prerender routes.
478
490
  dry_run: If True, do not write the compiled app to disk.
479
491
  check_if_schema_up_to_date: If True, check if the schema is up to date.
480
492
  """
481
493
  get_compiled_app(
482
494
  reload=reload,
483
- export=export,
495
+ prerender_routes=prerender_routes,
484
496
  dry_run=dry_run,
485
497
  check_if_schema_up_to_date=check_if_schema_up_to_date,
486
498
  )
@@ -529,20 +541,26 @@ def _can_colorize() -> bool:
529
541
 
530
542
 
531
543
  def compile_or_validate_app(
532
- compile: bool = False, check_if_schema_up_to_date: bool = False
544
+ compile: bool = False,
545
+ check_if_schema_up_to_date: bool = False,
546
+ prerender_routes: bool = False,
533
547
  ) -> bool:
534
548
  """Compile or validate the app module based on the default config.
535
549
 
536
550
  Args:
537
551
  compile: Whether to compile the app.
538
552
  check_if_schema_up_to_date: If True, check if the schema is up to date.
553
+ prerender_routes: Whether to prerender routes.
539
554
 
540
555
  Returns:
541
556
  If the app is compiled successfully.
542
557
  """
543
558
  try:
544
559
  if compile:
545
- compile_app(check_if_schema_up_to_date=check_if_schema_up_to_date)
560
+ compile_app(
561
+ check_if_schema_up_to_date=check_if_schema_up_to_date,
562
+ prerender_routes=prerender_routes,
563
+ )
546
564
  else:
547
565
  validate_app(check_if_schema_up_to_date=check_if_schema_up_to_date)
548
566
  except Exception as e:
@@ -591,19 +609,19 @@ def get_redis_sync() -> RedisSync | None:
591
609
 
592
610
 
593
611
  def parse_redis_url() -> str | None:
594
- """Parse the REDIS_URL in config if applicable.
612
+ """Parse the REFLEX_REDIS_URL in config if applicable.
595
613
 
596
614
  Returns:
597
615
  If url is non-empty, return the URL as it is.
598
616
 
599
617
  Raises:
600
- ValueError: If the REDIS_URL is not a supported scheme.
618
+ ValueError: If the REFLEX_REDIS_URL is not a supported scheme.
601
619
  """
602
620
  config = get_config()
603
621
  if not config.redis_url:
604
622
  return None
605
623
  if not config.redis_url.startswith(("redis://", "rediss://", "unix://")):
606
- msg = "REDIS_URL must start with 'redis://', 'rediss://', or 'unix://'."
624
+ msg = "REFLEX_REDIS_URL must start with 'redis://', 'rediss://', or 'unix://'."
607
625
  raise ValueError(msg)
608
626
  return config.redis_url
609
627
 
@@ -720,14 +738,11 @@ def rename_app(new_app_name: str, loglevel: constants.LogLevel):
720
738
  sys.path.insert(0, str(Path.cwd()))
721
739
 
722
740
  config = get_config()
723
- module_path = importlib.util.find_spec(config.module)
741
+ module_path = get_module_path(config.module)
724
742
  if module_path is None:
725
743
  console.error(f"Could not find module {config.module}.")
726
744
  raise click.exceptions.Exit(1)
727
745
 
728
- if not module_path.origin:
729
- console.error(f"Could not find origin for module {config.module}.")
730
- raise click.exceptions.Exit(1)
731
746
  console.info(f"Renaming app directory to {new_app_name}.")
732
747
  process_directory(
733
748
  Path.cwd(),
@@ -736,7 +751,7 @@ def rename_app(new_app_name: str, loglevel: constants.LogLevel):
736
751
  exclude_dirs=[constants.Dirs.WEB, constants.Dirs.APP_ASSETS],
737
752
  )
738
753
 
739
- rename_path_up_tree(Path(module_path.origin), config.app_name, new_app_name)
754
+ rename_path_up_tree(module_path, config.app_name, new_app_name)
740
755
 
741
756
  console.success(f"App directory renamed to [bold]{new_app_name}[/bold].")
742
757
 
@@ -1017,29 +1032,19 @@ def initialize_web_directory():
1017
1032
  console.debug("Initializing the public directory.")
1018
1033
  path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
1019
1034
 
1020
- console.debug("Initializing the next.config.js file.")
1021
- update_next_config()
1035
+ console.debug("Initializing the react-router.config.js file.")
1036
+ update_react_router_config()
1022
1037
 
1023
1038
  console.debug("Initializing the reflex.json file.")
1024
1039
  # Initialize the reflex json file.
1025
1040
  init_reflex_json(project_hash=project_hash)
1026
1041
 
1027
1042
 
1028
- @once
1029
- def _turbopack_flag() -> str:
1030
- return " --turbopack" if environment.REFLEX_USE_TURBOPACK.get() else ""
1031
-
1032
-
1033
1043
  def _compile_package_json():
1034
1044
  return templates.PACKAGE_JSON.render(
1035
1045
  scripts={
1036
- "dev": constants.PackageJson.Commands.DEV.format(flags=_turbopack_flag()),
1037
- "export": constants.PackageJson.Commands.EXPORT.format(
1038
- flags=_turbopack_flag()
1039
- ),
1040
- "export_sitemap": constants.PackageJson.Commands.EXPORT_SITEMAP.format(
1041
- flags=_turbopack_flag()
1042
- ),
1046
+ "dev": constants.PackageJson.Commands.DEV,
1047
+ "export": constants.PackageJson.Commands.EXPORT,
1043
1048
  "prod": constants.PackageJson.Commands.PROD,
1044
1049
  },
1045
1050
  dependencies=constants.PackageJson.DEPENDENCIES,
@@ -1107,50 +1112,43 @@ def init_reflex_json(project_hash: int | None):
1107
1112
  path_ops.update_json_file(get_web_dir() / constants.Reflex.JSON, reflex_json)
1108
1113
 
1109
1114
 
1110
- def update_next_config(
1111
- export: bool = False, transpile_packages: list[str] | None = None
1112
- ):
1113
- """Update Next.js config from Reflex config.
1115
+ def update_react_router_config(prerender_routes: bool = False):
1116
+ """Update react-router.config.js config from Reflex config.
1114
1117
 
1115
1118
  Args:
1116
- export: if the method run during reflex export.
1117
- transpile_packages: list of packages to transpile via next.config.js.
1119
+ prerender_routes: Whether to enable prerendering of routes.
1118
1120
  """
1119
- next_config_file = get_web_dir() / constants.Next.CONFIG_FILE
1121
+ react_router_config_file_path = get_web_dir() / constants.ReactRouter.CONFIG_FILE
1120
1122
 
1121
- next_config = _update_next_config(
1122
- get_config(), export=export, transpile_packages=transpile_packages
1123
+ new_react_router_config = _update_react_router_config(
1124
+ get_config(), prerender_routes=prerender_routes
1123
1125
  )
1124
1126
 
1125
- # Overwriting the next.config.js triggers a full server reload, so make sure
1127
+ # Overwriting the config file triggers a full server reload, so make sure
1126
1128
  # there is actually a diff.
1127
- orig_next_config = next_config_file.read_text() if next_config_file.exists() else ""
1128
- if orig_next_config != next_config:
1129
- next_config_file.write_text(next_config)
1129
+ old_react_router_config = (
1130
+ react_router_config_file_path.read_text()
1131
+ if react_router_config_file_path.exists()
1132
+ else ""
1133
+ )
1134
+ if old_react_router_config != new_react_router_config:
1135
+ react_router_config_file_path.write_text(new_react_router_config)
1130
1136
 
1131
1137
 
1132
- def _update_next_config(
1133
- config: Config, export: bool = False, transpile_packages: list[str] | None = None
1134
- ):
1135
- next_config = {
1136
- "basePath": config.frontend_path or "",
1137
- "compress": config.next_compression,
1138
- "trailingSlash": True,
1139
- "staticPageGenerationTimeout": config.static_page_generation_timeout,
1138
+ def _update_react_router_config(config: Config, prerender_routes: bool = False):
1139
+ react_router_config = {
1140
+ "basename": "/" + (config.frontend_path or "").removeprefix("/"),
1141
+ "future": {
1142
+ "unstable_optimizeDeps": True,
1143
+ },
1144
+ "ssr": False,
1140
1145
  }
1141
- if not config.next_dev_indicators:
1142
- next_config["devIndicators"] = False
1143
1146
 
1144
- if transpile_packages:
1145
- next_config["transpilePackages"] = list(
1146
- dict.fromkeys([format_library_name(p) for p in transpile_packages])
1147
- )
1148
- if export:
1149
- next_config["output"] = "export"
1150
- next_config["distDir"] = constants.Dirs.STATIC
1147
+ if prerender_routes:
1148
+ react_router_config["prerender"] = True
1149
+ react_router_config["build"] = constants.Dirs.BUILD_DIR
1151
1150
 
1152
- next_config_json = re.sub(r'"([^"]+)"(?=:)', r"\1", json.dumps(next_config))
1153
- return f"module.exports = {next_config_json};"
1151
+ return f"export default {json.dumps(react_router_config)};"
1154
1152
 
1155
1153
 
1156
1154
  def remove_existing_bun_installation():
reflex/utils/processes.py CHANGED
@@ -4,17 +4,18 @@ from __future__ import annotations
4
4
 
5
5
  import collections
6
6
  import contextlib
7
- import importlib.metadata
8
7
  import os
9
8
  import signal
9
+ import socket
10
10
  import subprocess
11
11
  from collections.abc import Callable, Generator, Sequence
12
12
  from concurrent import futures
13
+ from contextlib import closing
13
14
  from pathlib import Path
14
15
  from typing import Any, Literal, overload
15
16
 
16
17
  import click
17
- import psutil
18
+ import rich.markup
18
19
  from redis.exceptions import RedisError
19
20
  from rich.progress import Progress
20
21
 
@@ -52,29 +53,6 @@ def get_num_workers() -> int:
52
53
  return (os.cpu_count() or 1) * 2 + 1
53
54
 
54
55
 
55
- def get_process_on_port(port: int) -> psutil.Process | None:
56
- """Get the process on the given port.
57
-
58
- Args:
59
- port: The port.
60
-
61
- Returns:
62
- The process on the given port.
63
- """
64
- for proc in psutil.process_iter(["pid", "name", "cmdline"]):
65
- with contextlib.suppress(
66
- psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess
67
- ):
68
- if importlib.metadata.version("psutil") >= "6.0.0":
69
- conns = proc.net_connections(kind="inet")
70
- else:
71
- conns = proc.connections(kind="inet")
72
- for conn in conns:
73
- if conn.laddr.port == int(port):
74
- return proc
75
- return None
76
-
77
-
78
56
  def is_process_on_port(port: int) -> bool:
79
57
  """Check if a process is running on the given port.
80
58
 
@@ -84,18 +62,16 @@ def is_process_on_port(port: int) -> bool:
84
62
  Returns:
85
63
  Whether a process is running on the given port.
86
64
  """
87
- return get_process_on_port(port) is not None
65
+ # Test IPv4 localhost (127.0.0.1)
66
+ with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
67
+ ipv4_result = sock.connect_ex(("127.0.0.1", port)) == 0
88
68
 
69
+ # Test IPv6 localhost (::1)
70
+ with closing(socket.socket(socket.AF_INET6, socket.SOCK_STREAM)) as sock:
71
+ ipv6_result = sock.connect_ex(("::1", port)) == 0
89
72
 
90
- def kill_process_on_port(port: int):
91
- """Kill the process on the given port.
92
-
93
- Args:
94
- port: The port.
95
- """
96
- if get_process_on_port(port) is not None:
97
- with contextlib.suppress(psutil.AccessDenied):
98
- get_process_on_port(port).kill() # pyright: ignore [reportOptionalMemberAccess]
73
+ # Port is in use if either IPv4 or IPv6 is listening
74
+ return ipv4_result or ipv6_result
99
75
 
100
76
 
101
77
  def change_port(port: int, _type: str) -> int:
@@ -133,13 +109,13 @@ def handle_port(service_name: str, port: int, auto_increment: bool) -> int:
133
109
  Raises:
134
110
  Exit:when the port is in use.
135
111
  """
136
- if (process := get_process_on_port(port)) is None:
112
+ console.debug(f"Checking if {service_name.capitalize()} port: {port} is in use.")
113
+ if not is_process_on_port(port):
114
+ console.debug(f"{service_name.capitalize()} port: {port} is not in use.")
137
115
  return port
138
116
  if auto_increment:
139
117
  return change_port(port, service_name)
140
- console.error(
141
- f"{service_name.capitalize()} port: {port} is already in use by PID: {process.pid}."
142
- )
118
+ console.error(f"{service_name.capitalize()} port: {port} is already in use.")
143
119
  raise click.exceptions.Exit
144
120
 
145
121
 
@@ -306,7 +282,7 @@ def stream_logs(
306
282
  return
307
283
  try:
308
284
  for line in process.stdout:
309
- console.debug(line, end="", progress=progress)
285
+ console.debug(rich.markup.escape(line), end="", progress=progress)
310
286
  logs.append(line)
311
287
  yield line
312
288
  except ValueError:
@@ -320,7 +296,8 @@ def stream_logs(
320
296
 
321
297
  # Windows uvicorn bug
322
298
  # https://github.com/reflex-dev/reflex/issues/2335
323
- accepted_return_codes = [0, -2, 15] if constants.IS_WINDOWS else [0, -2]
299
+ # 130 is the exit code that react router returns when it is interrupted by a signal.
300
+ accepted_return_codes = [0, -2, 15, 130] if constants.IS_WINDOWS else [0, -2, 130]
324
301
  if process.returncode not in accepted_return_codes and not suppress_errors:
325
302
  console.error(f"{message} failed with exit code {process.returncode}")
326
303
  if "".join(logs).count("CERT_HAS_EXPIRED") > 0:
@@ -412,12 +389,12 @@ def show_progress(message: str, process: subprocess.Popen, checkpoints: list[str
412
389
  task = progress.add_task(f"{message}: ", total=len(checkpoints))
413
390
  for line in stream_logs(message, process, progress=progress):
414
391
  # Check for special strings and update the progress bar.
415
- for special_string in checkpoints:
416
- if special_string in line:
417
- progress.update(task, advance=1)
418
- if special_string == checkpoints[-1]:
419
- progress.update(task, completed=len(checkpoints))
420
- break
392
+ special_string = checkpoints[0]
393
+ if special_string in line:
394
+ progress.update(task, advance=1)
395
+ checkpoints.pop(0)
396
+ if not checkpoints:
397
+ break
421
398
 
422
399
 
423
400
  def atexit_handler():
@@ -13,7 +13,6 @@ import subprocess
13
13
  import sys
14
14
  import typing
15
15
  from collections.abc import Callable, Iterable, Sequence
16
- from fileinput import FileInput
17
16
  from hashlib import md5
18
17
  from inspect import getfullargspec
19
18
  from itertools import chain
@@ -67,7 +66,6 @@ OVERWRITE_TYPES = {
67
66
  }
68
67
 
69
68
  DEFAULT_TYPING_IMPORTS = {
70
- "overload",
71
69
  "Any",
72
70
  "Callable",
73
71
  "Dict",
@@ -90,6 +88,7 @@ DEFAULT_IMPORTS = {
90
88
  "EventSpec",
91
89
  "EventType",
92
90
  "KeyInputInfo",
91
+ "PointerEventInfo",
93
92
  ],
94
93
  "reflex.style": ["Style"],
95
94
  "reflex.vars.base": ["Var"],
@@ -366,7 +365,7 @@ def _extract_class_props_as_ast_nodes(
366
365
  all_props = []
367
366
  kwargs = []
368
367
  for target_class in clzs:
369
- event_triggers = target_class._create([]).get_event_triggers()
368
+ event_triggers = target_class.get_event_triggers()
370
369
  # Import from the target class to ensure type hints are resolvable.
371
370
  exec(f"from {target_class.__module__} import *", type_hint_globals)
372
371
  for name, value in target_class.__annotations__.items():
@@ -409,7 +408,7 @@ def _extract_class_props_as_ast_nodes(
409
408
  )
410
409
  ),
411
410
  ),
412
- ast.Constant(value=default),
411
+ ast.Constant(value=default), # pyright: ignore [reportArgumentType]
413
412
  )
414
413
  )
415
414
  return kwargs
@@ -444,7 +443,11 @@ def type_to_ast(typ: Any, cls: type) -> ast.expr:
444
443
 
445
444
  if all(a == b for a, b in zipped) and len(typ_parts) == len(cls_parts):
446
445
  return ast.Name(id=typ.__name__)
447
-
446
+ if (
447
+ typ.__module__ in DEFAULT_IMPORTS
448
+ and typ.__name__ in DEFAULT_IMPORTS[typ.__module__]
449
+ ):
450
+ return ast.Name(id=typ.__name__)
448
451
  return ast.Name(id=typ.__module__ + "." + typ.__name__)
449
452
  return ast.Name(id=typ.__name__)
450
453
  if hasattr(typ, "_name"):
@@ -607,7 +610,7 @@ def _generate_component_create_functiondef(
607
610
  return ast.Name(id=f"{' | '.join(map(ast.unparse, all_count_args_type))}")
608
611
  return ast.Name(id="EventType[Any]")
609
612
 
610
- event_triggers = clz._create([]).get_event_triggers()
613
+ event_triggers = clz.get_event_triggers()
611
614
 
612
615
  # event handler kwargs
613
616
  kwargs.extend(
@@ -672,10 +675,7 @@ def _generate_component_create_functiondef(
672
675
  value=ast.Constant(value=Ellipsis),
673
676
  ),
674
677
  ],
675
- decorator_list=[
676
- ast.Name(id="overload"),
677
- *decorator_list,
678
- ],
678
+ decorator_list=list(decorator_list),
679
679
  lineno=lineno,
680
680
  returns=ast.Constant(value=clz.__name__),
681
681
  )
@@ -891,7 +891,7 @@ class StubGenerator(ast.NodeTransformer):
891
891
  The modified ImportFrom node.
892
892
  """
893
893
  if node.module == "__future__":
894
- return None # ignore __future__ imports
894
+ return None # ignore __future__ imports: https://docs.astral.sh/ruff/rules/future-annotations-in-stub/
895
895
  return self.visit_Import(node)
896
896
 
897
897
  def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef:
@@ -1104,9 +1104,10 @@ class PyiGenerator:
1104
1104
 
1105
1105
  def _get_init_lazy_imports(self, mod: tuple | ModuleType, new_tree: ast.AST):
1106
1106
  # retrieve the _SUBMODULES and _SUBMOD_ATTRS from an init file if present.
1107
- sub_mods = getattr(mod, "_SUBMODULES", None)
1108
- sub_mod_attrs = getattr(mod, "_SUBMOD_ATTRS", None)
1109
- pyright_ignore_imports = getattr(mod, "_PYRIGHT_IGNORE_IMPORTS", [])
1107
+ sub_mods: set[str] | None = getattr(mod, "_SUBMODULES", None)
1108
+ sub_mod_attrs: dict[str, list[str | tuple[str, str]]] | None = getattr(
1109
+ mod, "_SUBMOD_ATTRS", None
1110
+ )
1110
1111
 
1111
1112
  if not sub_mods and not sub_mod_attrs:
1112
1113
  return None
@@ -1114,31 +1115,34 @@ class PyiGenerator:
1114
1115
  sub_mod_attrs_imports = []
1115
1116
 
1116
1117
  if sub_mods:
1117
- sub_mods_imports = [
1118
- f"from . import {mod} as {mod}" for mod in sorted(sub_mods)
1119
- ]
1118
+ sub_mods_imports = [f"from . import {mod}" for mod in sorted(sub_mods)]
1120
1119
  sub_mods_imports.append("")
1121
1120
 
1122
1121
  if sub_mod_attrs:
1123
- sub_mod_attrs = {
1124
- attr: mod for mod, attrs in sub_mod_attrs.items() for attr in attrs
1122
+ flattened_sub_mod_attrs = {
1123
+ imported: module
1124
+ for module, attrs in sub_mod_attrs.items()
1125
+ for imported in attrs
1125
1126
  }
1126
1127
  # construct the import statement and handle special cases for aliases
1127
1128
  sub_mod_attrs_imports = [
1128
- f"from .{path} import {mod if not isinstance(mod, tuple) else mod[0]} as {mod if not isinstance(mod, tuple) else mod[1]}"
1129
+ f"from .{module} import "
1129
1130
  + (
1130
- " # type: ignore"
1131
- if mod in pyright_ignore_imports
1132
- else " # noqa: F401" # ignore ruff formatting here for cases like rx.list.
1133
- if isinstance(mod, tuple)
1134
- else ""
1131
+ (
1132
+ (imported[0] + " as " + imported[1])
1133
+ if imported[0] != imported[1]
1134
+ else imported[0]
1135
+ )
1136
+ if isinstance(imported, tuple)
1137
+ else imported
1135
1138
  )
1136
- for mod, path in sub_mod_attrs.items()
1139
+ for imported, module in flattened_sub_mod_attrs.items()
1137
1140
  ]
1138
1141
  sub_mod_attrs_imports.append("")
1139
1142
 
1140
1143
  text = "\n" + "\n".join([*sub_mods_imports, *sub_mod_attrs_imports])
1141
- text += ast.unparse(new_tree) + "\n"
1144
+ text += ast.unparse(new_tree) + "\n\n"
1145
+ text += f"__all__ = {getattr(mod, '__all__', [])!r}\n"
1142
1146
  return text
1143
1147
 
1144
1148
  def _scan_file(self, module_path: Path) -> tuple[str, str] | None:
@@ -1253,10 +1257,7 @@ class PyiGenerator:
1253
1257
  if file_paths:
1254
1258
  subprocess.run(["ruff", "format", *file_paths])
1255
1259
  subprocess.run(["ruff", "check", "--fix", *file_paths])
1256
-
1257
- # For some reason, we need to format the __init__.pyi files again after fixing...
1258
- init_files = [f for f in file_paths if "/__init__.pyi" in f]
1259
- subprocess.run(["ruff", "format", *init_files])
1260
+ subprocess.run(["ruff", "format", *file_paths])
1260
1261
 
1261
1262
  if use_json:
1262
1263
  if file_paths and changed_files is None:
@@ -1322,27 +1323,21 @@ class PyiGenerator:
1322
1323
  json.dumps(pyi_hashes, indent=2, sort_keys=True) + "\n"
1323
1324
  )
1324
1325
 
1325
- # Post-process the generated pyi files to add hacky type: ignore comments
1326
- for file_path in file_paths:
1327
- with FileInput(file_path, inplace=True) as f:
1328
- for line in f:
1329
- # Hack due to ast not supporting comments in the tree.
1330
- if (
1331
- "def create(" in line
1332
- or "Var[Figure]" in line
1333
- or "Var[Template]" in line
1334
- ):
1335
- line = line.rstrip() + " # type: ignore\n"
1336
- print(line, end="") # noqa: T201
1337
-
1338
1326
 
1339
1327
  if __name__ == "__main__":
1328
+ import argparse
1329
+
1330
+ parser = argparse.ArgumentParser(description="Generate .pyi stub files")
1331
+ parser.add_argument(
1332
+ "targets",
1333
+ nargs="*",
1334
+ default=["reflex/components", "reflex/experimental", "reflex/__init__.py"],
1335
+ help="Target directories/files to process",
1336
+ )
1337
+ args = parser.parse_args()
1338
+
1340
1339
  logging.basicConfig(level=logging.INFO)
1341
1340
  logging.getLogger("blib2to3.pgen2.driver").setLevel(logging.INFO)
1342
1341
 
1343
1342
  gen = PyiGenerator()
1344
- gen.scan_all(
1345
- ["reflex/components", "reflex/experimental", "reflex/__init__.py"],
1346
- None,
1347
- use_json=True,
1348
- )
1343
+ gen.scan_all(args.targets, None, use_json=True)
@@ -9,7 +9,7 @@ import functools
9
9
  import inspect
10
10
  import json
11
11
  import warnings
12
- from collections.abc import Callable, Sequence
12
+ from collections.abc import Callable, Mapping, Sequence
13
13
  from datetime import date, datetime, time, timedelta
14
14
  from enum import Enum
15
15
  from pathlib import Path
@@ -335,6 +335,19 @@ def serialize_sequence(value: Sequence) -> list:
335
335
  return list(value)
336
336
 
337
337
 
338
+ @serializer(to=dict)
339
+ def serialize_mapping(value: Mapping) -> dict:
340
+ """Serialize a mapping type to a dictionary.
341
+
342
+ Args:
343
+ value: The mapping instance to serialize.
344
+
345
+ Returns:
346
+ A new dictionary containing the same key-value pairs as the input mapping.
347
+ """
348
+ return {**value}
349
+
350
+
338
351
  @serializer(to=str)
339
352
  def serialize_datetime(dt: date | datetime | time | timedelta) -> str:
340
353
  """Serialize a datetime to a JSON string.