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.
- reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/_app.js.jinja2 +7 -7
- reflex/.templates/jinja/web/pages/utils.js.jinja2 +2 -2
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +1 -4
- reflex/.templates/web/utils/state.js +65 -36
- reflex/__init__.py +4 -17
- reflex/__init__.pyi +1 -2
- reflex/app.py +244 -109
- reflex/app_mixins/lifespan.py +9 -9
- reflex/app_mixins/middleware.py +6 -6
- reflex/app_module_for_backend.py +3 -7
- reflex/base.py +7 -7
- reflex/compiler/compiler.py +8 -0
- reflex/compiler/utils.py +35 -6
- reflex/components/base/bare.py +1 -1
- reflex/components/base/error_boundary.py +2 -1
- reflex/components/base/error_boundary.pyi +2 -1
- reflex/components/base/meta.py +2 -2
- reflex/components/base/strict_mode.py +10 -0
- reflex/components/base/strict_mode.pyi +57 -0
- reflex/components/component.py +38 -77
- reflex/components/core/banner.py +83 -4
- reflex/components/core/banner.pyi +86 -0
- reflex/components/core/breakpoints.py +3 -1
- reflex/components/core/client_side_routing.py +1 -1
- reflex/components/core/client_side_routing.pyi +1 -1
- reflex/components/core/cond.py +9 -10
- reflex/components/core/debounce.py +1 -1
- reflex/components/core/foreach.py +23 -3
- reflex/components/core/html.py +1 -1
- reflex/components/core/match.py +5 -5
- reflex/components/core/sticky.py +160 -0
- reflex/components/core/sticky.pyi +449 -0
- reflex/components/core/upload.py +2 -2
- reflex/components/datadisplay/code.py +5 -14
- reflex/components/datadisplay/dataeditor.py +7 -4
- reflex/components/datadisplay/logo.py +13 -8
- reflex/components/datadisplay/shiki_code_block.py +14 -9
- reflex/components/dynamic.py +22 -3
- reflex/components/el/constants/reflex.py +1 -1
- reflex/components/el/element.py +1 -1
- reflex/components/el/elements/forms.py +4 -4
- reflex/components/el/elements/forms.pyi +4 -4
- reflex/components/lucide/icon.py +46 -8
- reflex/components/lucide/icon.pyi +54 -0
- reflex/components/markdown/markdown.py +10 -8
- reflex/components/moment/moment.py +2 -2
- reflex/components/next/image.py +16 -4
- reflex/components/next/image.pyi +4 -2
- reflex/components/next/link.py +1 -1
- reflex/components/plotly/plotly.py +5 -5
- reflex/components/props.py +3 -3
- reflex/components/radix/__init__.pyi +1 -1
- reflex/components/radix/primitives/accordion.py +9 -5
- reflex/components/radix/primitives/accordion.pyi +3 -1
- reflex/components/radix/primitives/drawer.py +5 -2
- reflex/components/radix/primitives/drawer.pyi +4 -4
- reflex/components/radix/primitives/form.pyi +6 -6
- reflex/components/radix/primitives/progress.py +1 -1
- reflex/components/radix/primitives/slider.py +1 -1
- reflex/components/radix/themes/color_mode.py +11 -9
- reflex/components/radix/themes/components/alert_dialog.py +3 -0
- reflex/components/radix/themes/components/card.py +1 -1
- reflex/components/radix/themes/components/card.pyi +1 -1
- reflex/components/radix/themes/components/context_menu.py +5 -0
- reflex/components/radix/themes/components/dialog.py +3 -0
- reflex/components/radix/themes/components/dropdown_menu.py +5 -0
- reflex/components/radix/themes/components/hover_card.py +3 -0
- reflex/components/radix/themes/components/icon_button.py +2 -2
- reflex/components/radix/themes/components/icon_button.pyi +1 -0
- reflex/components/radix/themes/components/popover.py +3 -0
- reflex/components/radix/themes/components/radio_cards.py +2 -0
- reflex/components/radix/themes/components/radio_group.py +1 -1
- reflex/components/radix/themes/components/select.py +3 -0
- reflex/components/radix/themes/components/tabs.py +3 -0
- reflex/components/radix/themes/components/text_area.py +12 -0
- reflex/components/radix/themes/components/text_area.pyi +2 -0
- reflex/components/radix/themes/components/text_field.py +1 -1
- reflex/components/radix/themes/components/tooltip.py +3 -1
- reflex/components/radix/themes/components/tooltip.pyi +1 -0
- reflex/components/radix/themes/layout/__init__.pyi +1 -1
- reflex/components/radix/themes/layout/list.py +2 -2
- reflex/components/radix/themes/layout/stack.py +2 -2
- reflex/components/radix/themes/typography/link.py +1 -1
- reflex/components/radix/themes/typography/text.py +2 -2
- reflex/components/react_player/react_player.py +1 -1
- reflex/components/recharts/__init__.py +2 -0
- reflex/components/recharts/__init__.pyi +2 -0
- reflex/components/recharts/charts.py +15 -15
- reflex/components/recharts/general.py +19 -4
- reflex/components/recharts/general.pyi +55 -4
- reflex/components/recharts/polar.py +2 -2
- reflex/components/recharts/recharts.py +4 -4
- reflex/components/sonner/toast.py +15 -13
- reflex/components/sonner/toast.pyi +6 -6
- reflex/components/suneditor/editor.py +6 -4
- reflex/components/suneditor/editor.pyi +2 -2
- reflex/components/tags/iter_tag.py +3 -3
- reflex/components/tags/tag.py +25 -3
- reflex/config.py +48 -15
- reflex/constants/__init__.py +1 -0
- reflex/constants/base.py +4 -1
- reflex/constants/compiler.py +5 -2
- reflex/constants/config.py +8 -1
- reflex/constants/installer.py +9 -9
- reflex/constants/style.py +1 -1
- reflex/custom_components/custom_components.py +9 -7
- reflex/event.py +130 -161
- reflex/experimental/__init__.py +19 -11
- reflex/experimental/client_state.py +53 -28
- reflex/experimental/hooks.py +5 -5
- reflex/experimental/layout.py +8 -5
- reflex/experimental/layout.pyi +1 -1
- reflex/experimental/misc.py +3 -3
- reflex/istate/wrappers.py +1 -1
- reflex/middleware/hydrate_middleware.py +2 -2
- reflex/model.py +11 -6
- reflex/page.py +3 -3
- reflex/reflex.py +90 -19
- reflex/route.py +1 -1
- reflex/state.py +358 -401
- reflex/style.py +27 -3
- reflex/testing.py +29 -23
- reflex/utils/build.py +6 -2
- reflex/utils/codespaces.py +1 -4
- reflex/utils/compat.py +6 -5
- reflex/utils/console.py +52 -16
- reflex/utils/exceptions.py +76 -26
- reflex/utils/exec.py +69 -74
- reflex/utils/export.py +6 -1
- reflex/utils/format.py +7 -39
- reflex/utils/imports.py +2 -2
- reflex/utils/lazy_loader.py +7 -1
- reflex/utils/path_ops.py +28 -14
- reflex/utils/prerequisites.py +324 -65
- reflex/utils/processes.py +45 -32
- reflex/utils/pyi_generator.py +30 -25
- reflex/utils/registry.py +4 -4
- reflex/utils/serializers.py +1 -1
- reflex/utils/telemetry.py +5 -4
- reflex/utils/types.py +42 -18
- reflex/vars/base.py +650 -333
- reflex/vars/datetime.py +6 -7
- reflex/vars/dep_tracking.py +344 -0
- reflex/vars/function.py +11 -5
- reflex/vars/number.py +31 -43
- reflex/vars/object.py +63 -62
- reflex/vars/sequence.py +79 -67
- {reflex-0.6.8a2.dist-info → reflex-0.7.0a1.dist-info}/METADATA +7 -8
- {reflex-0.6.8a2.dist-info → reflex-0.7.0a1.dist-info}/RECORD +153 -149
- {reflex-0.6.8a2.dist-info → reflex-0.7.0a1.dist-info}/WHEEL +1 -1
- reflex/experimental/assets.py +0 -37
- {reflex-0.6.8a2.dist-info → reflex-0.7.0a1.dist-info}/LICENSE +0 -0
- {reflex-0.6.8a2.dist-info → reflex-0.7.0a1.dist-info}/entry_points.txt +0 -0
reflex/utils/prerequisites.py
CHANGED
|
@@ -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
|
-
|
|
40
|
-
|
|
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
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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) #
|
|
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]) #
|
|
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(
|
|
202
|
-
return version.parse(result.stdout) #
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
872
|
-
|
|
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"],
|
|
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
|
-
|
|
1076
|
-
|
|
1077
|
-
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
"
|
|
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})
|