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