reflex 0.7.4a3__py3-none-any.whl → 0.7.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of reflex might be problematic. Click here for more details.

Files changed (160) hide show
  1. reflex/__init__.py +1 -0
  2. reflex/__init__.pyi +1 -0
  3. reflex/app.py +10 -6
  4. reflex/app_mixins/middleware.py +13 -20
  5. reflex/compiler/compiler.py +10 -3
  6. reflex/compiler/utils.py +4 -4
  7. reflex/components/base/app_wrap.pyi +7 -3
  8. reflex/components/base/body.pyi +7 -3
  9. reflex/components/base/document.pyi +27 -7
  10. reflex/components/base/error_boundary.pyi +7 -3
  11. reflex/components/base/fragment.pyi +7 -3
  12. reflex/components/base/head.pyi +12 -4
  13. reflex/components/base/link.pyi +12 -4
  14. reflex/components/base/meta.pyi +22 -6
  15. reflex/components/base/script.pyi +7 -3
  16. reflex/components/base/strict_mode.pyi +7 -3
  17. reflex/components/component.py +64 -23
  18. reflex/components/core/auto_scroll.pyi +7 -3
  19. reflex/components/core/banner.py +6 -2
  20. reflex/components/core/banner.pyi +32 -8
  21. reflex/components/core/client_side_routing.pyi +12 -4
  22. reflex/components/core/clipboard.pyi +7 -3
  23. reflex/components/core/debounce.pyi +7 -3
  24. reflex/components/core/foreach.py +5 -1
  25. reflex/components/core/html.pyi +7 -3
  26. reflex/components/core/match.py +5 -5
  27. reflex/components/core/sticky.pyi +21 -6
  28. reflex/components/core/upload.pyi +27 -7
  29. reflex/components/datadisplay/code.pyi +12 -4
  30. reflex/components/datadisplay/dataeditor.py +2 -2
  31. reflex/components/datadisplay/dataeditor.pyi +17 -3
  32. reflex/components/datadisplay/shiki_code_block.pyi +17 -4
  33. reflex/components/el/__init__.pyi +1 -1
  34. reflex/components/el/element.pyi +7 -3
  35. reflex/components/el/elements/__init__.py +3 -1
  36. reflex/components/el/elements/__init__.pyi +3 -2
  37. reflex/components/el/elements/base.pyi +7 -3
  38. reflex/components/el/elements/forms.py +1 -1
  39. reflex/components/el/elements/forms.pyi +72 -16
  40. reflex/components/el/elements/inline.pyi +142 -30
  41. reflex/components/el/elements/media.pyi +127 -27
  42. reflex/components/el/elements/metadata.pyi +32 -8
  43. reflex/components/el/elements/other.pyi +37 -9
  44. reflex/components/el/elements/scripts.pyi +17 -5
  45. reflex/components/el/elements/sectioning.pyi +77 -17
  46. reflex/components/el/elements/tables.pyi +52 -12
  47. reflex/components/el/elements/typography.pyi +77 -17
  48. reflex/components/gridjs/datatable.py +2 -2
  49. reflex/components/gridjs/datatable.pyi +12 -4
  50. reflex/components/lucide/icon.py +7 -6
  51. reflex/components/lucide/icon.pyi +22 -7
  52. reflex/components/markdown/markdown.py +1 -1
  53. reflex/components/markdown/markdown.pyi +7 -3
  54. reflex/components/moment/moment.pyi +7 -3
  55. reflex/components/next/base.pyi +7 -3
  56. reflex/components/next/image.pyi +7 -3
  57. reflex/components/next/link.pyi +7 -3
  58. reflex/components/next/video.pyi +7 -3
  59. reflex/components/plotly/plotly.pyi +47 -11
  60. reflex/components/radix/primitives/accordion.pyi +37 -9
  61. reflex/components/radix/primitives/base.pyi +12 -4
  62. reflex/components/radix/primitives/drawer.pyi +57 -13
  63. reflex/components/radix/primitives/form.pyi +52 -12
  64. reflex/components/radix/primitives/progress.pyi +27 -7
  65. reflex/components/radix/primitives/slider.pyi +27 -7
  66. reflex/components/radix/themes/base.pyi +41 -10
  67. reflex/components/radix/themes/color_mode.py +2 -2
  68. reflex/components/radix/themes/color_mode.pyi +17 -5
  69. reflex/components/radix/themes/components/alert_dialog.pyi +36 -9
  70. reflex/components/radix/themes/components/aspect_ratio.pyi +7 -3
  71. reflex/components/radix/themes/components/avatar.pyi +6 -3
  72. reflex/components/radix/themes/components/badge.pyi +6 -3
  73. reflex/components/radix/themes/components/button.pyi +6 -3
  74. reflex/components/radix/themes/components/callout.pyi +26 -7
  75. reflex/components/radix/themes/components/card.pyi +6 -3
  76. reflex/components/radix/themes/components/checkbox.pyi +16 -5
  77. reflex/components/radix/themes/components/checkbox_cards.pyi +11 -4
  78. reflex/components/radix/themes/components/checkbox_group.pyi +11 -4
  79. reflex/components/radix/themes/components/context_menu.pyi +66 -15
  80. reflex/components/radix/themes/components/data_list.pyi +21 -6
  81. reflex/components/radix/themes/components/dialog.pyi +36 -9
  82. reflex/components/radix/themes/components/dropdown_menu.pyi +41 -10
  83. reflex/components/radix/themes/components/hover_card.pyi +21 -6
  84. reflex/components/radix/themes/components/icon_button.pyi +6 -3
  85. reflex/components/radix/themes/components/inset.pyi +6 -3
  86. reflex/components/radix/themes/components/popover.pyi +21 -6
  87. reflex/components/radix/themes/components/progress.pyi +6 -3
  88. reflex/components/radix/themes/components/radio.pyi +6 -3
  89. reflex/components/radix/themes/components/radio_cards.pyi +11 -4
  90. reflex/components/radix/themes/components/radio_group.py +6 -1
  91. reflex/components/radix/themes/components/radio_group.pyi +21 -6
  92. reflex/components/radix/themes/components/scroll_area.pyi +7 -3
  93. reflex/components/radix/themes/components/segmented_control.pyi +11 -4
  94. reflex/components/radix/themes/components/select.pyi +46 -11
  95. reflex/components/radix/themes/components/separator.pyi +6 -3
  96. reflex/components/radix/themes/components/skeleton.pyi +6 -3
  97. reflex/components/radix/themes/components/slider.pyi +6 -3
  98. reflex/components/radix/themes/components/spinner.pyi +6 -3
  99. reflex/components/radix/themes/components/switch.pyi +6 -3
  100. reflex/components/radix/themes/components/table.pyi +36 -9
  101. reflex/components/radix/themes/components/tabs.pyi +26 -7
  102. reflex/components/radix/themes/components/text_area.pyi +6 -3
  103. reflex/components/radix/themes/components/text_field.py +3 -2
  104. reflex/components/radix/themes/components/text_field.pyi +16 -5
  105. reflex/components/radix/themes/components/tooltip.pyi +7 -3
  106. reflex/components/radix/themes/layout/base.pyi +6 -3
  107. reflex/components/radix/themes/layout/box.pyi +7 -3
  108. reflex/components/radix/themes/layout/center.pyi +6 -3
  109. reflex/components/radix/themes/layout/container.pyi +6 -3
  110. reflex/components/radix/themes/layout/flex.pyi +6 -3
  111. reflex/components/radix/themes/layout/grid.pyi +6 -3
  112. reflex/components/radix/themes/layout/list.pyi +27 -7
  113. reflex/components/radix/themes/layout/section.pyi +6 -3
  114. reflex/components/radix/themes/layout/spacer.pyi +6 -3
  115. reflex/components/radix/themes/layout/stack.pyi +16 -5
  116. reflex/components/radix/themes/typography/blockquote.pyi +6 -3
  117. reflex/components/radix/themes/typography/code.pyi +6 -3
  118. reflex/components/radix/themes/typography/heading.pyi +6 -3
  119. reflex/components/radix/themes/typography/link.pyi +6 -3
  120. reflex/components/radix/themes/typography/text.pyi +36 -9
  121. reflex/components/react_player/audio.pyi +7 -3
  122. reflex/components/react_player/react_player.pyi +7 -3
  123. reflex/components/react_player/video.pyi +7 -3
  124. reflex/components/recharts/cartesian.pyi +97 -21
  125. reflex/components/recharts/charts.pyi +62 -14
  126. reflex/components/recharts/general.pyi +32 -8
  127. reflex/components/recharts/polar.py +1 -1
  128. reflex/components/recharts/polar.pyi +33 -9
  129. reflex/components/recharts/recharts.pyi +12 -4
  130. reflex/components/sonner/toast.pyi +7 -2
  131. reflex/components/suneditor/editor.pyi +7 -3
  132. reflex/config.py +15 -1
  133. reflex/constants/installer.py +22 -1
  134. reflex/custom_components/custom_components.py +12 -7
  135. reflex/event.py +26 -10
  136. reflex/experimental/__init__.py +17 -6
  137. reflex/experimental/layout.pyi +27 -7
  138. reflex/model.py +3 -3
  139. reflex/reflex.py +33 -18
  140. reflex/state.py +3 -3
  141. reflex/style.py +2 -2
  142. reflex/testing.py +17 -5
  143. reflex/utils/console.py +2 -3
  144. reflex/utils/exec.py +4 -0
  145. reflex/utils/imports.py +14 -7
  146. reflex/utils/prerequisites.py +72 -7
  147. reflex/utils/processes.py +52 -19
  148. reflex/utils/pyi_generator.py +66 -53
  149. reflex/utils/registry.py +5 -3
  150. reflex/utils/serializers.py +1 -2
  151. reflex/utils/types.py +4 -4
  152. reflex/vars/base.py +58 -22
  153. reflex/vars/number.py +23 -6
  154. reflex/vars/sequence.py +2 -0
  155. {reflex-0.7.4a3.dist-info → reflex-0.7.5.dist-info}/METADATA +2 -2
  156. {reflex-0.7.4a3.dist-info → reflex-0.7.5.dist-info}/RECORD +160 -160
  157. /reflex/{experimental → utils}/misc.py +0 -0
  158. {reflex-0.7.4a3.dist-info → reflex-0.7.5.dist-info}/WHEEL +0 -0
  159. {reflex-0.7.4a3.dist-info → reflex-0.7.5.dist-info}/entry_points.txt +0 -0
  160. {reflex-0.7.4a3.dist-info → reflex-0.7.5.dist-info}/licenses/LICENSE +0 -0
reflex/reflex.py CHANGED
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import atexit
6
6
  from pathlib import Path
7
+ from typing import TYPE_CHECKING
7
8
 
8
9
  import typer
9
10
  import typer.core
@@ -429,7 +430,7 @@ def logout(
429
430
 
430
431
  loglevel = loglevel or get_config().loglevel
431
432
 
432
- logout(loglevel) # pyright: ignore [reportArgumentType]
433
+ logout(_convert_reflex_loglevel_to_reflex_cli_loglevel(loglevel))
433
434
 
434
435
 
435
436
  db_cli = typer.Typer()
@@ -581,7 +582,6 @@ def deploy(
581
582
  ),
582
583
  ):
583
584
  """Deploy the app to the Reflex hosting service."""
584
- from reflex_cli.constants.base import LogLevel as HostingLogLevel
585
585
  from reflex_cli.utils import dependency
586
586
  from reflex_cli.v2 import cli as hosting_cli
587
587
  from reflex_cli.v2.deployments import check_version
@@ -604,21 +604,6 @@ def deploy(
604
604
  # Set the log level.
605
605
  console.set_log_level(loglevel)
606
606
 
607
- def convert_reflex_loglevel_to_reflex_cli_loglevel(
608
- loglevel: constants.LogLevel,
609
- ) -> HostingLogLevel:
610
- if loglevel == constants.LogLevel.DEBUG:
611
- return HostingLogLevel.DEBUG
612
- if loglevel == constants.LogLevel.INFO:
613
- return HostingLogLevel.INFO
614
- if loglevel == constants.LogLevel.WARNING:
615
- return HostingLogLevel.WARNING
616
- if loglevel == constants.LogLevel.ERROR:
617
- return HostingLogLevel.ERROR
618
- if loglevel == constants.LogLevel.CRITICAL:
619
- return HostingLogLevel.CRITICAL
620
- return HostingLogLevel.INFO
621
-
622
607
  # Only check requirements if interactive.
623
608
  # There is user interaction for requirements update.
624
609
  if interactive:
@@ -652,7 +637,7 @@ def deploy(
652
637
  envfile=envfile,
653
638
  hostname=hostname,
654
639
  interactive=interactive,
655
- loglevel=convert_reflex_loglevel_to_reflex_cli_loglevel(loglevel),
640
+ loglevel=_convert_reflex_loglevel_to_reflex_cli_loglevel(loglevel),
656
641
  token=token,
657
642
  project=project,
658
643
  project_name=project_name,
@@ -676,6 +661,36 @@ def rename(
676
661
  prerequisites.rename_app(new_name, loglevel)
677
662
 
678
663
 
664
+ if TYPE_CHECKING:
665
+ from reflex_cli.constants.base import LogLevel as HostingLogLevel
666
+
667
+
668
+ def _convert_reflex_loglevel_to_reflex_cli_loglevel(
669
+ loglevel: constants.LogLevel,
670
+ ) -> HostingLogLevel:
671
+ """Convert a Reflex log level to a Reflex CLI log level.
672
+
673
+ Args:
674
+ loglevel: The Reflex log level to convert.
675
+
676
+ Returns:
677
+ The converted Reflex CLI log level.
678
+ """
679
+ from reflex_cli.constants.base import LogLevel as HostingLogLevel
680
+
681
+ if loglevel == constants.LogLevel.DEBUG:
682
+ return HostingLogLevel.DEBUG
683
+ if loglevel == constants.LogLevel.INFO:
684
+ return HostingLogLevel.INFO
685
+ if loglevel == constants.LogLevel.WARNING:
686
+ return HostingLogLevel.WARNING
687
+ if loglevel == constants.LogLevel.ERROR:
688
+ return HostingLogLevel.ERROR
689
+ if loglevel == constants.LogLevel.CRITICAL:
690
+ return HostingLogLevel.CRITICAL
691
+ return HostingLogLevel.INFO
692
+
693
+
679
694
  cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
680
695
  cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")
681
696
  cli.add_typer(
reflex/state.py CHANGED
@@ -593,8 +593,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
593
593
  if cls._item_is_event_handler(name, fn)
594
594
  }
595
595
 
596
- for mixin in cls._mixins(): # pyright: ignore [reportAssignmentType]
597
- for name, value in mixin.__dict__.items():
596
+ for mixin_cls in cls._mixins():
597
+ for name, value in mixin_cls.__dict__.items():
598
598
  if name in cls.inherited_vars:
599
599
  continue
600
600
  if is_computed_var(value):
@@ -605,7 +605,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
605
605
  cls.computed_vars[newcv._js_expr] = newcv
606
606
  cls.vars[newcv._js_expr] = newcv
607
607
  continue
608
- if types.is_backend_base_variable(name, mixin): # pyright: ignore [reportArgumentType]
608
+ if types.is_backend_base_variable(name, mixin_cls):
609
609
  cls.backend_vars[name] = copy.deepcopy(value)
610
610
  continue
611
611
  if events.get(name) is not None:
reflex/style.py CHANGED
@@ -237,10 +237,10 @@ def format_style_key(key: str) -> tuple[str, ...]:
237
237
  EMPTY_VAR_DATA = VarData()
238
238
 
239
239
 
240
- class Style(dict):
240
+ class Style(dict[str, Any]):
241
241
  """A style dictionary."""
242
242
 
243
- def __init__(self, style_dict: dict | None = None, **kwargs):
243
+ def __init__(self, style_dict: dict[str, Any] | None = None, **kwargs):
244
244
  """Initialize the style.
245
245
 
246
246
  Args:
reflex/testing.py CHANGED
@@ -27,6 +27,7 @@ from typing import (
27
27
  Callable,
28
28
  Coroutine,
29
29
  Optional,
30
+ Sequence,
30
31
  Type,
31
32
  TypeVar,
32
33
  )
@@ -268,6 +269,10 @@ class AppHarness:
268
269
  loglevel=reflex.constants.LogLevel.INFO,
269
270
  )
270
271
  self.app_module_path.write_text(source_code)
272
+ else:
273
+ # Just initialize the web folder.
274
+ with chdir(self.app_path):
275
+ reflex.utils.prerequisites.initialize_frontend_dependencies()
271
276
  with chdir(self.app_path):
272
277
  # ensure config and app are reloaded when testing different app
273
278
  reflex.config.get_config(reload=True)
@@ -290,8 +295,10 @@ class AppHarness:
290
295
  if self.app_instance and isinstance(
291
296
  self.app_instance._state_manager, StateManagerRedis
292
297
  ):
298
+ if self.app_instance._state is None:
299
+ raise RuntimeError("State is not set.")
293
300
  # Create our own redis connection for testing.
294
- self.state_manager = StateManagerRedis.create(self.app_instance._state) # pyright: ignore [reportArgumentType]
301
+ self.state_manager = StateManagerRedis.create(self.app_instance._state)
295
302
  else:
296
303
  self.state_manager = (
297
304
  self.app_instance._state_manager if self.app_instance else None
@@ -455,6 +462,10 @@ class AppHarness:
455
462
 
456
463
  def stop(self) -> None:
457
464
  """Stop the frontend and backend servers."""
465
+ # Quit browsers first to avoid any lingering events being sent during shutdown.
466
+ for driver in self._frontends:
467
+ driver.quit()
468
+
458
469
  self._reload_state_module()
459
470
 
460
471
  if self.backend is not None:
@@ -485,8 +496,6 @@ class AppHarness:
485
496
  self.backend_thread.join()
486
497
  if self.frontend_output_thread is not None:
487
498
  self.frontend_output_thread.join()
488
- for driver in self._frontends:
489
- driver.quit()
490
499
 
491
500
  # Cleanup decorated pages added during testing
492
501
  for page in self._decorated_pages:
@@ -764,7 +773,7 @@ class AppHarness:
764
773
  self,
765
774
  element: "WebElement",
766
775
  timeout: TimeoutType = None,
767
- exp_not_equal: str = "",
776
+ exp_not_equal: str | Sequence[str] = "",
768
777
  ) -> str | None:
769
778
  """Poll element.get_attribute("value") for change.
770
779
 
@@ -779,8 +788,11 @@ class AppHarness:
779
788
  Raises:
780
789
  TimeoutError: when the timeout expires before value changes
781
790
  """
791
+ exp_not_equal = (
792
+ (exp_not_equal,) if isinstance(exp_not_equal, str) else exp_not_equal
793
+ )
782
794
  if not self._poll_for(
783
- target=lambda: element.get_attribute("value") != exp_not_equal,
795
+ target=lambda: element.get_attribute("value") not in exp_not_equal,
784
796
  timeout=timeout,
785
797
  ):
786
798
  raise TimeoutError(
reflex/utils/console.py CHANGED
@@ -201,10 +201,9 @@ def _get_first_non_framework_frame() -> FrameType | None:
201
201
  # Exclude utility modules that should never be the source of deprecated reflex usage.
202
202
  exclude_modules = [click, rx, typer, typing_extensions]
203
203
  exclude_roots = [
204
- p.parent.resolve()
205
- if (p := Path(m.__file__)).name == "__init__.py" # pyright: ignore [reportArgumentType]
206
- else p.resolve()
204
+ p.parent.resolve() if (p := Path(file)).name == "__init__.py" else p.resolve()
207
205
  for m in exclude_modules
206
+ if (file := m.__file__)
208
207
  ]
209
208
  # Specifically exclude the reflex cli module.
210
209
  if reflex_bin := shutil.which(b"reflex"):
reflex/utils/exec.py CHANGED
@@ -330,6 +330,7 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
330
330
  log_level=loglevel.value,
331
331
  reload=True,
332
332
  reload_dirs=list(map(str, get_reload_paths())),
333
+ reload_delay=0.1,
333
334
  )
334
335
 
335
336
 
@@ -356,6 +357,9 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
356
357
  log_level=LogLevels(loglevel.value),
357
358
  reload=True,
358
359
  reload_paths=get_reload_paths(),
360
+ reload_ignore_worker_failure=True,
361
+ reload_tick=100,
362
+ workers_kill_timeout=2,
359
363
  ).serve()
360
364
 
361
365
 
reflex/utils/imports.py CHANGED
@@ -4,11 +4,11 @@ from __future__ import annotations
4
4
 
5
5
  import dataclasses
6
6
  from collections import defaultdict
7
- from typing import DefaultDict, Union
7
+ from typing import DefaultDict, Mapping, Sequence, Union
8
8
 
9
9
 
10
10
  def merge_imports(
11
- *imports: ImportDict | ParsedImportDict | ImmutableParsedImportDict,
11
+ *imports: ImportDict | ParsedImportDict | ParsedImportTuple,
12
12
  ) -> ParsedImportDict:
13
13
  """Merge multiple import dicts together.
14
14
 
@@ -43,7 +43,9 @@ def merge_imports(
43
43
  return all_imports
44
44
 
45
45
 
46
- def parse_imports(imports: ImportDict | ParsedImportDict) -> ParsedImportDict:
46
+ def parse_imports(
47
+ imports: ImmutableImportDict | ImmutableParsedImportDict,
48
+ ) -> ParsedImportDict:
47
49
  """Parse the import dict into a standard format.
48
50
 
49
51
  Args:
@@ -53,10 +55,12 @@ def parse_imports(imports: ImportDict | ParsedImportDict) -> ParsedImportDict:
53
55
  The parsed import dict.
54
56
  """
55
57
 
56
- def _make_list(value: ImportTypes) -> list[str | ImportVar] | list[ImportVar]:
58
+ def _make_list(
59
+ value: ImmutableImportTypes,
60
+ ) -> list[str | ImportVar] | list[ImportVar]:
57
61
  if isinstance(value, (str, ImportVar)):
58
62
  return [value]
59
- return value
63
+ return list(value)
60
64
 
61
65
  return {
62
66
  package: [
@@ -68,7 +72,7 @@ def parse_imports(imports: ImportDict | ParsedImportDict) -> ParsedImportDict:
68
72
 
69
73
 
70
74
  def collapse_imports(
71
- imports: ParsedImportDict | ImmutableParsedImportDict,
75
+ imports: ParsedImportDict | ParsedImportTuple,
72
76
  ) -> ParsedImportDict:
73
77
  """Remove all duplicate ImportVar within an ImportDict.
74
78
 
@@ -132,6 +136,9 @@ class ImportVar:
132
136
 
133
137
 
134
138
  ImportTypes = Union[str, ImportVar, list[str | ImportVar], list[ImportVar]]
139
+ ImmutableImportTypes = Union[str, ImportVar, Sequence[str | ImportVar]]
135
140
  ImportDict = dict[str, ImportTypes]
141
+ ImmutableImportDict = Mapping[str, ImmutableImportTypes]
136
142
  ParsedImportDict = dict[str, list[ImportVar]]
137
- ImmutableParsedImportDict = tuple[tuple[str, tuple[ImportVar, ...]], ...]
143
+ ImmutableParsedImportDict = Mapping[str, Sequence[ImportVar]]
144
+ ParsedImportTuple = tuple[tuple[str, tuple[ImportVar, ...]], ...]
@@ -510,6 +510,9 @@ def compile_or_validate_app(compile: bool = False) -> bool:
510
510
  else:
511
511
  validate_app()
512
512
  except Exception as e:
513
+ if isinstance(e, typer.Exit):
514
+ return False
515
+
513
516
  import traceback
514
517
 
515
518
  sys_exception = sys.exception()
@@ -963,6 +966,9 @@ def initialize_web_directory():
963
966
  console.debug("Initializing the bun config file.")
964
967
  initialize_bun_config()
965
968
 
969
+ console.debug("Initializing the .npmrc file.")
970
+ initialize_npmrc()
971
+
966
972
  console.debug("Initializing the public directory.")
967
973
  path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
968
974
 
@@ -1012,6 +1018,20 @@ def initialize_bun_config():
1012
1018
  bun_config_path.write_text(bunfig_content)
1013
1019
 
1014
1020
 
1021
+ def initialize_npmrc():
1022
+ """Initialize the .npmrc file."""
1023
+ npmrc_path = get_web_dir() / constants.Node.CONFIG_PATH
1024
+
1025
+ if (custom_npmrc := Path(constants.Node.CONFIG_PATH)).exists():
1026
+ npmrc_content = custom_npmrc.read_text()
1027
+ console.info(f"Copying custom .npmrc inside {get_web_dir()} folder")
1028
+ else:
1029
+ best_registry = get_npm_registry()
1030
+ npmrc_content = constants.Node.DEFAULT_CONFIG.format(registry=best_registry)
1031
+
1032
+ npmrc_path.write_text(npmrc_content)
1033
+
1034
+
1015
1035
  def init_reflex_json(project_hash: int | None):
1016
1036
  """Write the hash of the Reflex project to a REFLEX_JSON.
1017
1037
 
@@ -1067,6 +1087,7 @@ def _update_next_config(
1067
1087
  "compress": config.next_compression,
1068
1088
  "trailingSlash": True,
1069
1089
  "staticPageGenerationTimeout": config.static_page_generation_timeout,
1090
+ "devIndicators": config.next_dev_indicators,
1070
1091
  }
1071
1092
  if transpile_packages:
1072
1093
  next_config["transpilePackages"] = list(
@@ -1180,26 +1201,39 @@ def _clear_cached_procedure_file(cache_file: str | Path):
1180
1201
  cache_file.unlink()
1181
1202
 
1182
1203
 
1183
- def cached_procedure(cache_file: str, payload_fn: Callable[..., str]):
1204
+ def cached_procedure(
1205
+ cache_file: str | None,
1206
+ payload_fn: Callable[..., str],
1207
+ cache_file_fn: Callable[[], str] | None = None,
1208
+ ):
1184
1209
  """Decorator to cache the runs of a procedure on disk. Procedures should not have
1185
1210
  a return value.
1186
1211
 
1187
1212
  Args:
1188
1213
  cache_file: The file to store the cache payload in.
1189
- payload_fn: Function that computes cache payload from function args
1214
+ payload_fn: Function that computes cache payload from function args.
1215
+ cache_file_fn: Function that computes the cache file name at runtime.
1190
1216
 
1191
1217
  Returns:
1192
1218
  The decorated function.
1219
+
1220
+ Raises:
1221
+ ValueError: If both cache_file and cache_file_fn are provided.
1193
1222
  """
1223
+ if cache_file and cache_file_fn is not None:
1224
+ raise ValueError("cache_file and cache_file_fn cannot both be provided.")
1194
1225
 
1195
1226
  def _inner_decorator(func: Callable):
1196
1227
  def _inner(*args, **kwargs):
1197
- payload = _read_cached_procedure_file(cache_file)
1228
+ _cache_file = cache_file_fn() if cache_file_fn is not None else cache_file
1229
+ if not _cache_file:
1230
+ raise ValueError("Unknown cache file, cannot cache result.")
1231
+ payload = _read_cached_procedure_file(_cache_file)
1198
1232
  new_payload = payload_fn(*args, **kwargs)
1199
1233
  if payload != new_payload:
1200
- _clear_cached_procedure_file(cache_file)
1234
+ _clear_cached_procedure_file(_cache_file)
1201
1235
  func(*args, **kwargs)
1202
- _write_cached_procedure_file(new_payload, cache_file)
1236
+ _write_cached_procedure_file(new_payload, _cache_file)
1203
1237
 
1204
1238
  return _inner
1205
1239
 
@@ -1207,8 +1241,11 @@ def cached_procedure(cache_file: str, payload_fn: Callable[..., str]):
1207
1241
 
1208
1242
 
1209
1243
  @cached_procedure(
1210
- cache_file=str(get_web_dir() / "reflex.install_frontend_packages.cached"),
1244
+ cache_file_fn=lambda: str(
1245
+ get_web_dir() / "reflex.install_frontend_packages.cached"
1246
+ ),
1211
1247
  payload_fn=lambda p, c: f"{sorted(p)!r},{c.json()}",
1248
+ cache_file=None,
1212
1249
  )
1213
1250
  def install_frontend_packages(packages: set[str], config: Config):
1214
1251
  """Installs the base and custom frontend packages.
@@ -1224,6 +1261,14 @@ def install_frontend_packages(packages: set[str], config: Config):
1224
1261
  raise_on_none=True
1225
1262
  )
1226
1263
 
1264
+ env = (
1265
+ {
1266
+ "NODE_TLS_REJECT_UNAUTHORIZED": "0",
1267
+ }
1268
+ if environment.SSL_NO_VERIFY.get()
1269
+ else {}
1270
+ )
1271
+
1227
1272
  primary_package_manager = install_package_managers[0]
1228
1273
  fallbacks = install_package_managers[1:]
1229
1274
 
@@ -1234,6 +1279,7 @@ def install_frontend_packages(packages: set[str], config: Config):
1234
1279
  show_status_message="Installing base frontend packages",
1235
1280
  cwd=get_web_dir(),
1236
1281
  shell=constants.IS_WINDOWS,
1282
+ env=env,
1237
1283
  )
1238
1284
 
1239
1285
  if config.tailwind is not None:
@@ -1251,6 +1297,7 @@ def install_frontend_packages(packages: set[str], config: Config):
1251
1297
  show_status_message="Installing tailwind",
1252
1298
  cwd=get_web_dir(),
1253
1299
  shell=constants.IS_WINDOWS,
1300
+ env=env,
1254
1301
  )
1255
1302
 
1256
1303
  # Install custom packages defined in frontend_packages
@@ -1262,6 +1309,7 @@ def install_frontend_packages(packages: set[str], config: Config):
1262
1309
  show_status_message="Installing frontend packages from config and components",
1263
1310
  cwd=get_web_dir(),
1264
1311
  shell=constants.IS_WINDOWS,
1312
+ env=env,
1265
1313
  )
1266
1314
 
1267
1315
 
@@ -1510,6 +1558,9 @@ def prompt_for_template_options(templates: list[Template]) -> str:
1510
1558
 
1511
1559
  Returns:
1512
1560
  The template name the user selects.
1561
+
1562
+ Raises:
1563
+ Exit: If the user does not select a template.
1513
1564
  """
1514
1565
  # Show the user the URLs of each template to preview.
1515
1566
  console.print("\nGet started with a template:")
@@ -1534,8 +1585,22 @@ def prompt_for_template_options(templates: list[Template]) -> str:
1534
1585
  default="0",
1535
1586
  )
1536
1587
 
1588
+ if not template:
1589
+ console.error("No template selected.")
1590
+ raise typer.Exit(1)
1591
+
1592
+ try:
1593
+ template_index = int(template)
1594
+ except ValueError:
1595
+ console.error("Invalid template selected.")
1596
+ raise typer.Exit(1) from None
1597
+
1598
+ if template_index < 0 or template_index >= len(templates):
1599
+ console.error("Invalid template selected.")
1600
+ raise typer.Exit(1)
1601
+
1537
1602
  # Return the template.
1538
- return templates[int(template)].name # pyright: ignore [reportArgumentType]
1603
+ return templates[template_index].name
1539
1604
 
1540
1605
 
1541
1606
  def fetch_app_templates(version: str) -> dict[str, Template]:
reflex/utils/processes.py CHANGED
@@ -20,6 +20,7 @@ from rich.progress import Progress
20
20
  from reflex import constants
21
21
  from reflex.config import environment
22
22
  from reflex.utils import console, path_ops, prerequisites
23
+ from reflex.utils.registry import get_npm_registry
23
24
 
24
25
 
25
26
  def kill(pid: int):
@@ -276,6 +277,7 @@ def stream_logs(
276
277
  progress: Progress | None = None,
277
278
  suppress_errors: bool = False,
278
279
  analytics_enabled: bool = False,
280
+ prior_logs: Tuple[tuple[str, ...], ...] = (),
279
281
  ):
280
282
  """Stream the logs for a process.
281
283
 
@@ -285,6 +287,7 @@ def stream_logs(
285
287
  progress: The ongoing progress bar if one is being used.
286
288
  suppress_errors: If True, do not exit if errors are encountered (for fallback).
287
289
  analytics_enabled: Whether analytics are enabled for this command.
290
+ prior_logs: The logs of the prior processes that have been run.
288
291
 
289
292
  Yields:
290
293
  The lines of the process output.
@@ -312,8 +315,31 @@ def stream_logs(
312
315
  accepted_return_codes = [0, -2, 15] if constants.IS_WINDOWS else [0, -2]
313
316
  if process.returncode not in accepted_return_codes and not suppress_errors:
314
317
  console.error(f"{message} failed with exit code {process.returncode}")
315
- for line in logs:
316
- console.error(line, end="")
318
+ if "".join(logs).count("CERT_HAS_EXPIRED") > 0:
319
+ bunfig = prerequisites.get_web_dir() / constants.Bun.CONFIG_PATH
320
+ npm_registry_line = next(
321
+ (
322
+ line
323
+ for line in bunfig.read_text().splitlines()
324
+ if line.startswith("registry")
325
+ ),
326
+ None,
327
+ )
328
+ if not npm_registry_line or "=" not in npm_registry_line:
329
+ npm_registry = get_npm_registry()
330
+ else:
331
+ npm_registry = npm_registry_line.split("=")[1].strip()
332
+ console.error(
333
+ f"Failed to fetch securely from [bold]{npm_registry}[/bold]. Please check your network connection. "
334
+ "You can try running the command again or changing the registry by setting the "
335
+ "NPM_CONFIG_REGISTRY environment variable. If TLS is the issue, and you know what "
336
+ "you are doing, you can disable it by setting the SSL_NO_VERIFY environment variable."
337
+ )
338
+ raise typer.Exit(1)
339
+ for set_of_logs in (*prior_logs, tuple(logs)):
340
+ for line in set_of_logs:
341
+ console.error(line, end="")
342
+ console.error("\n\n")
317
343
  if analytics_enabled:
318
344
  telemetry.send("error", context=message)
319
345
  console.error("Run with [bold]--loglevel debug [/bold] for the full log.")
@@ -336,8 +362,8 @@ def show_status(
336
362
  process: subprocess.Popen,
337
363
  suppress_errors: bool = False,
338
364
  analytics_enabled: bool = False,
339
- prior_processes: Tuple[subprocess.Popen, ...] = (),
340
- ):
365
+ prior_logs: Tuple[tuple[str, ...], ...] = (),
366
+ ) -> list[str]:
341
367
  """Show the status of a process.
342
368
 
343
369
  Args:
@@ -345,17 +371,24 @@ def show_status(
345
371
  process: The process.
346
372
  suppress_errors: If True, do not exit if errors are encountered (for fallback).
347
373
  analytics_enabled: Whether analytics are enabled for this command.
348
- prior_processes: The prior processes that have been run.
374
+ prior_logs: The logs of the prior processes that have been run.
375
+
376
+ Returns:
377
+ The lines of the process output.
349
378
  """
350
- for one_process in (*prior_processes, process):
351
- with console.status(message) as status:
352
- for line in stream_logs(
353
- message,
354
- one_process,
355
- suppress_errors=suppress_errors,
356
- analytics_enabled=analytics_enabled,
357
- ):
358
- status.update(f"{message} {line}")
379
+ lines = []
380
+
381
+ with console.status(message) as status:
382
+ for line in stream_logs(
383
+ message,
384
+ process,
385
+ suppress_errors=suppress_errors,
386
+ analytics_enabled=analytics_enabled,
387
+ prior_logs=prior_logs,
388
+ ):
389
+ status.update(f"{message} {line}")
390
+ lines.append(line)
391
+ return lines
359
392
 
360
393
 
361
394
  def show_progress(message: str, process: subprocess.Popen, checkpoints: list[str]):
@@ -409,7 +442,7 @@ def run_process_with_fallbacks(
409
442
  show_status_message: str,
410
443
  fallbacks: str | Sequence[str] | Sequence[Sequence[str]] | None = None,
411
444
  analytics_enabled: bool = False,
412
- prior_processes: Tuple[subprocess.Popen, ...] = (),
445
+ prior_logs: Tuple[tuple[str, ...], ...] = (),
413
446
  **kwargs,
414
447
  ):
415
448
  """Run subprocess and retry using fallback command if initial command fails.
@@ -419,7 +452,7 @@ def run_process_with_fallbacks(
419
452
  show_status_message: The status message to be displayed in the console.
420
453
  fallbacks: The fallback command to run if the initial command fails.
421
454
  analytics_enabled: Whether analytics are enabled for this command.
422
- prior_processes: The prior processes that have been run.
455
+ prior_logs: The logs of the prior processes that have been run.
423
456
  **kwargs: Kwargs to pass to new_process function.
424
457
  """
425
458
  process = new_process(get_command_with_loglevel(args), **kwargs)
@@ -429,11 +462,11 @@ def run_process_with_fallbacks(
429
462
  show_status_message,
430
463
  process,
431
464
  analytics_enabled=analytics_enabled,
432
- prior_processes=prior_processes,
465
+ prior_logs=prior_logs,
433
466
  )
434
467
  else:
435
468
  # Suppress errors for initial command, because we will try to fallback
436
- show_status(show_status_message, process, suppress_errors=True)
469
+ logs = show_status(show_status_message, process, suppress_errors=True)
437
470
 
438
471
  current_fallback = fallbacks[0] if not isinstance(fallbacks, str) else fallbacks
439
472
  next_fallbacks = fallbacks[1:] if not isinstance(fallbacks, str) else None
@@ -453,7 +486,7 @@ def run_process_with_fallbacks(
453
486
  show_status_message=show_status_message,
454
487
  fallbacks=next_fallbacks,
455
488
  analytics_enabled=analytics_enabled,
456
- prior_processes=(*prior_processes, process),
489
+ prior_logs=(*prior_logs, tuple(logs)),
457
490
  **kwargs,
458
491
  )
459
492