reflex 0.8.6a0__py3-none-any.whl → 0.8.7a1__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/web/vite.config.js.jinja2 +4 -1
- reflex/.templates/web/app/routes.js +0 -1
- reflex/.templates/web/utils/state.js +1 -11
- reflex/app.py +27 -23
- reflex/components/base/error_boundary.py +2 -0
- reflex/components/component.py +6 -4
- reflex/components/lucide/icon.py +4 -1
- reflex/components/lucide/icon.pyi +4 -1
- reflex/components/plotly/plotly.py +9 -9
- reflex/components/recharts/recharts.py +2 -2
- reflex/components/sonner/toast.py +7 -7
- reflex/components/sonner/toast.pyi +8 -8
- reflex/config.py +9 -2
- reflex/constants/base.py +2 -0
- reflex/constants/installer.py +6 -6
- reflex/constants/state.py +1 -0
- reflex/custom_components/custom_components.py +3 -3
- reflex/istate/manager.py +2 -1
- reflex/plugins/__init__.py +2 -0
- reflex/plugins/_screenshot.py +144 -0
- reflex/plugins/base.py +14 -1
- reflex/reflex.py +7 -6
- reflex/route.py +4 -0
- reflex/state.py +2 -2
- reflex/testing.py +3 -5
- reflex/utils/build.py +21 -3
- reflex/utils/exec.py +11 -11
- reflex/utils/frontend_skeleton.py +254 -0
- reflex/utils/js_runtimes.py +411 -0
- reflex/utils/prerequisites.py +17 -1383
- reflex/utils/rename.py +170 -0
- reflex/utils/telemetry.py +101 -10
- reflex/utils/templates.py +443 -0
- reflex/vars/base.py +3 -3
- {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/METADATA +2 -2
- {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/RECORD +39 -36
- reflex/.templates/web/utils/client_side_routing.js +0 -45
- reflex/components/core/client_side_routing.py +0 -70
- {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/WHEEL +0 -0
- {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/entry_points.txt +0 -0
- {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/licenses/LICENSE +0 -0
reflex/utils/prerequisites.py
CHANGED
|
@@ -3,26 +3,17 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import contextlib
|
|
6
|
-
import dataclasses
|
|
7
|
-
import functools
|
|
8
6
|
import importlib
|
|
9
7
|
import importlib.metadata
|
|
10
8
|
import json
|
|
11
|
-
import os
|
|
12
|
-
import platform
|
|
13
9
|
import random
|
|
14
10
|
import re
|
|
15
|
-
import shutil
|
|
16
11
|
import sys
|
|
17
|
-
import tempfile
|
|
18
12
|
import typing
|
|
19
|
-
import zipfile
|
|
20
|
-
from collections.abc import Sequence
|
|
21
13
|
from datetime import datetime
|
|
22
14
|
from pathlib import Path
|
|
23
15
|
from types import ModuleType
|
|
24
16
|
from typing import NamedTuple
|
|
25
|
-
from urllib.parse import urlparse
|
|
26
17
|
|
|
27
18
|
import click
|
|
28
19
|
from alembic.util.exc import CommandError
|
|
@@ -32,14 +23,11 @@ from redis.asyncio import Redis
|
|
|
32
23
|
from redis.exceptions import RedisError
|
|
33
24
|
|
|
34
25
|
from reflex import constants, model
|
|
35
|
-
from reflex.compiler import templates
|
|
36
26
|
from reflex.config import Config, get_config
|
|
37
27
|
from reflex.environment import environment
|
|
38
|
-
from reflex.utils import console, net, path_ops
|
|
39
|
-
from reflex.utils.decorator import
|
|
40
|
-
from reflex.utils.exceptions import SystemPackageMissingError
|
|
28
|
+
from reflex.utils import console, net, path_ops
|
|
29
|
+
from reflex.utils.decorator import once
|
|
41
30
|
from reflex.utils.misc import get_module_path
|
|
42
|
-
from reflex.utils.registry import get_npm_registry
|
|
43
31
|
|
|
44
32
|
if typing.TYPE_CHECKING:
|
|
45
33
|
from reflex.app import App
|
|
@@ -52,24 +40,6 @@ class AppInfo(NamedTuple):
|
|
|
52
40
|
module: ModuleType
|
|
53
41
|
|
|
54
42
|
|
|
55
|
-
@dataclasses.dataclass(frozen=True)
|
|
56
|
-
class Template:
|
|
57
|
-
"""A template for a Reflex app."""
|
|
58
|
-
|
|
59
|
-
name: str
|
|
60
|
-
description: str
|
|
61
|
-
code_url: str
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
@dataclasses.dataclass(frozen=True)
|
|
65
|
-
class CpuInfo:
|
|
66
|
-
"""Model to save cpu info."""
|
|
67
|
-
|
|
68
|
-
manufacturer_id: str | None
|
|
69
|
-
model_name: str | None
|
|
70
|
-
address_width: int | None
|
|
71
|
-
|
|
72
|
-
|
|
73
43
|
def get_web_dir() -> Path:
|
|
74
44
|
"""Get the working directory for the frontend.
|
|
75
45
|
|
|
@@ -157,161 +127,7 @@ def set_last_reflex_run_time():
|
|
|
157
127
|
)
|
|
158
128
|
|
|
159
129
|
|
|
160
|
-
|
|
161
|
-
"""Check the version of Node.js.
|
|
162
|
-
|
|
163
|
-
Returns:
|
|
164
|
-
Whether the version of Node.js is valid.
|
|
165
|
-
"""
|
|
166
|
-
current_version = get_node_version()
|
|
167
|
-
return current_version is not None and current_version >= version.parse(
|
|
168
|
-
constants.Node.MIN_VERSION
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def get_node_version() -> version.Version | None:
|
|
173
|
-
"""Get the version of node.
|
|
174
|
-
|
|
175
|
-
Returns:
|
|
176
|
-
The version of node.
|
|
177
|
-
"""
|
|
178
|
-
node_path = path_ops.get_node_path()
|
|
179
|
-
if node_path is None:
|
|
180
|
-
return None
|
|
181
|
-
try:
|
|
182
|
-
result = processes.new_process([node_path, "-v"], run=True)
|
|
183
|
-
# The output will be in the form "vX.Y.Z", but version.parse() can handle it
|
|
184
|
-
return version.parse(result.stdout)
|
|
185
|
-
except (FileNotFoundError, TypeError):
|
|
186
|
-
return None
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def get_bun_version(bun_path: Path | None = None) -> version.Version | None:
|
|
190
|
-
"""Get the version of bun.
|
|
191
|
-
|
|
192
|
-
Args:
|
|
193
|
-
bun_path: The path to the bun executable.
|
|
194
|
-
|
|
195
|
-
Returns:
|
|
196
|
-
The version of bun.
|
|
197
|
-
"""
|
|
198
|
-
bun_path = bun_path or path_ops.get_bun_path()
|
|
199
|
-
if bun_path is None:
|
|
200
|
-
return None
|
|
201
|
-
try:
|
|
202
|
-
# Run the bun -v command and capture the output
|
|
203
|
-
result = processes.new_process([str(bun_path), "-v"], run=True)
|
|
204
|
-
return version.parse(str(result.stdout))
|
|
205
|
-
except FileNotFoundError:
|
|
206
|
-
return None
|
|
207
|
-
except version.InvalidVersion as e:
|
|
208
|
-
console.warn(
|
|
209
|
-
f"The detected bun version ({e.args[0]}) is not valid. Defaulting to None."
|
|
210
|
-
)
|
|
211
|
-
return None
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def prefer_npm_over_bun() -> bool:
|
|
215
|
-
"""Check if npm should be preferred over bun.
|
|
216
|
-
|
|
217
|
-
Returns:
|
|
218
|
-
If npm should be preferred over bun.
|
|
219
|
-
"""
|
|
220
|
-
return npm_escape_hatch() or (
|
|
221
|
-
constants.IS_WINDOWS and windows_check_onedrive_in_path()
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
def get_nodejs_compatible_package_managers(
|
|
226
|
-
raise_on_none: bool = True,
|
|
227
|
-
) -> Sequence[str]:
|
|
228
|
-
"""Get the package manager executable for installation. Typically, bun is used for installation.
|
|
229
|
-
|
|
230
|
-
Args:
|
|
231
|
-
raise_on_none: Whether to raise an error if the package manager is not found.
|
|
232
|
-
|
|
233
|
-
Returns:
|
|
234
|
-
The path to the package manager.
|
|
235
|
-
|
|
236
|
-
Raises:
|
|
237
|
-
FileNotFoundError: If the package manager is not found and raise_on_none is True.
|
|
238
|
-
"""
|
|
239
|
-
bun_package_manager = (
|
|
240
|
-
str(bun_path) if (bun_path := path_ops.get_bun_path()) else None
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
npm_package_manager = (
|
|
244
|
-
str(npm_path) if (npm_path := path_ops.get_npm_path()) else None
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
if prefer_npm_over_bun():
|
|
248
|
-
package_managers = [npm_package_manager, bun_package_manager]
|
|
249
|
-
else:
|
|
250
|
-
package_managers = [bun_package_manager, npm_package_manager]
|
|
251
|
-
|
|
252
|
-
package_managers = list(filter(None, package_managers))
|
|
253
|
-
|
|
254
|
-
if not package_managers and raise_on_none:
|
|
255
|
-
msg = "Bun or npm not found. You might need to rerun `reflex init` or install either."
|
|
256
|
-
raise FileNotFoundError(msg)
|
|
257
|
-
|
|
258
|
-
return package_managers
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def is_outdated_nodejs_installed():
|
|
262
|
-
"""Check if the installed Node.js version is outdated.
|
|
263
|
-
|
|
264
|
-
Returns:
|
|
265
|
-
If the installed Node.js version is outdated.
|
|
266
|
-
"""
|
|
267
|
-
current_version = get_node_version()
|
|
268
|
-
if current_version is not None and current_version < version.parse(
|
|
269
|
-
constants.Node.MIN_VERSION
|
|
270
|
-
):
|
|
271
|
-
console.warn(
|
|
272
|
-
f"Your version ({current_version}) of Node.js is out of date. Upgrade to {constants.Node.MIN_VERSION} or higher."
|
|
273
|
-
)
|
|
274
|
-
return True
|
|
275
|
-
return False
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def get_js_package_executor(raise_on_none: bool = False) -> Sequence[Sequence[str]]:
|
|
279
|
-
"""Get the paths to package managers for running commands. Ordered by preference.
|
|
280
|
-
This is currently identical to get_install_package_managers, but may change in the future.
|
|
281
|
-
|
|
282
|
-
Args:
|
|
283
|
-
raise_on_none: Whether to raise an error if no package managers is not found.
|
|
284
|
-
|
|
285
|
-
Returns:
|
|
286
|
-
The paths to the package managers as a list of lists, where each list is the command to run and its arguments.
|
|
287
|
-
|
|
288
|
-
Raises:
|
|
289
|
-
FileNotFoundError: If no package managers are found and raise_on_none is True.
|
|
290
|
-
"""
|
|
291
|
-
bun_package_manager = (
|
|
292
|
-
[str(bun_path)] + (["--bun"] if is_outdated_nodejs_installed() else [])
|
|
293
|
-
if (bun_path := path_ops.get_bun_path())
|
|
294
|
-
else None
|
|
295
|
-
)
|
|
296
|
-
|
|
297
|
-
npm_package_manager = (
|
|
298
|
-
[str(npm_path)] if (npm_path := path_ops.get_npm_path()) else None
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
if prefer_npm_over_bun():
|
|
302
|
-
package_managers = [npm_package_manager, bun_package_manager]
|
|
303
|
-
else:
|
|
304
|
-
package_managers = [bun_package_manager, npm_package_manager]
|
|
305
|
-
|
|
306
|
-
package_managers = list(filter(None, package_managers))
|
|
307
|
-
|
|
308
|
-
if not package_managers and raise_on_none:
|
|
309
|
-
msg = "Bun or npm not found. You might need to rerun `reflex init` or install either."
|
|
310
|
-
raise FileNotFoundError(msg)
|
|
311
|
-
|
|
312
|
-
return package_managers
|
|
313
|
-
|
|
314
|
-
|
|
130
|
+
@once
|
|
315
131
|
def windows_check_onedrive_in_path() -> bool:
|
|
316
132
|
"""For windows, check if oneDrive is present in the project dir path.
|
|
317
133
|
|
|
@@ -321,15 +137,6 @@ def windows_check_onedrive_in_path() -> bool:
|
|
|
321
137
|
return "onedrive" in str(Path.cwd()).lower()
|
|
322
138
|
|
|
323
139
|
|
|
324
|
-
def npm_escape_hatch() -> bool:
|
|
325
|
-
"""If the user sets REFLEX_USE_NPM, prefer npm over bun.
|
|
326
|
-
|
|
327
|
-
Returns:
|
|
328
|
-
If the user has set REFLEX_USE_NPM.
|
|
329
|
-
"""
|
|
330
|
-
return environment.REFLEX_USE_NPM.get()
|
|
331
|
-
|
|
332
|
-
|
|
333
140
|
def _check_app_name(config: Config):
|
|
334
141
|
"""Check if the app name is valid and matches the folder structure.
|
|
335
142
|
|
|
@@ -347,7 +154,7 @@ def _check_app_name(config: Config):
|
|
|
347
154
|
)
|
|
348
155
|
raise RuntimeError(msg)
|
|
349
156
|
|
|
350
|
-
from reflex.utils.misc import
|
|
157
|
+
from reflex.utils.misc import with_cwd_in_syspath
|
|
351
158
|
|
|
352
159
|
with with_cwd_in_syspath():
|
|
353
160
|
module_path = get_module_path(config.module)
|
|
@@ -358,6 +165,7 @@ def _check_app_name(config: Config):
|
|
|
358
165
|
else:
|
|
359
166
|
msg += f"Ensure app_name='{config.app_name}' in rxconfig.py matches your folder structure."
|
|
360
167
|
raise ModuleNotFoundError(msg)
|
|
168
|
+
config._app_name_is_valid = True
|
|
361
169
|
|
|
362
170
|
|
|
363
171
|
def get_app(reload: bool = False) -> ModuleType:
|
|
@@ -377,7 +185,9 @@ def get_app(reload: bool = False) -> ModuleType:
|
|
|
377
185
|
try:
|
|
378
186
|
config = get_config()
|
|
379
187
|
|
|
380
|
-
|
|
188
|
+
# Avoid hitting disk when the app name has already been validated in this process.
|
|
189
|
+
if not config._app_name_is_valid:
|
|
190
|
+
_check_app_name(config)
|
|
381
191
|
|
|
382
192
|
module = config.module
|
|
383
193
|
sys.path.insert(0, str(Path.cwd()))
|
|
@@ -433,20 +243,6 @@ def get_and_validate_app(
|
|
|
433
243
|
return AppInfo(app=app, module=app_module)
|
|
434
244
|
|
|
435
245
|
|
|
436
|
-
def validate_app(
|
|
437
|
-
reload: bool = False, check_if_schema_up_to_date: bool = False
|
|
438
|
-
) -> None:
|
|
439
|
-
"""Validate the app instance based on the default config.
|
|
440
|
-
|
|
441
|
-
Args:
|
|
442
|
-
reload: Re-import the app module from disk
|
|
443
|
-
check_if_schema_up_to_date: If True, check if the schema is up to date.
|
|
444
|
-
"""
|
|
445
|
-
get_and_validate_app(
|
|
446
|
-
reload=reload, check_if_schema_up_to_date=check_if_schema_up_to_date
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
|
|
450
246
|
def get_compiled_app(
|
|
451
247
|
reload: bool = False,
|
|
452
248
|
prerender_routes: bool = False,
|
|
@@ -467,35 +263,10 @@ def get_compiled_app(
|
|
|
467
263
|
app, app_module = get_and_validate_app(
|
|
468
264
|
reload=reload, check_if_schema_up_to_date=check_if_schema_up_to_date
|
|
469
265
|
)
|
|
470
|
-
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
|
|
471
|
-
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
|
472
|
-
app._apply_decorated_pages()
|
|
473
266
|
app._compile(prerender_routes=prerender_routes, dry_run=dry_run)
|
|
474
267
|
return app_module
|
|
475
268
|
|
|
476
269
|
|
|
477
|
-
def compile_app(
|
|
478
|
-
reload: bool = False,
|
|
479
|
-
prerender_routes: bool = False,
|
|
480
|
-
dry_run: bool = False,
|
|
481
|
-
check_if_schema_up_to_date: bool = False,
|
|
482
|
-
) -> None:
|
|
483
|
-
"""Compile the app module based on the default config.
|
|
484
|
-
|
|
485
|
-
Args:
|
|
486
|
-
reload: Re-import the app module from disk
|
|
487
|
-
prerender_routes: Whether to prerender routes.
|
|
488
|
-
dry_run: If True, do not write the compiled app to disk.
|
|
489
|
-
check_if_schema_up_to_date: If True, check if the schema is up to date.
|
|
490
|
-
"""
|
|
491
|
-
get_compiled_app(
|
|
492
|
-
reload=reload,
|
|
493
|
-
prerender_routes=prerender_routes,
|
|
494
|
-
dry_run=dry_run,
|
|
495
|
-
check_if_schema_up_to_date=check_if_schema_up_to_date,
|
|
496
|
-
)
|
|
497
|
-
|
|
498
|
-
|
|
499
270
|
def compile_or_validate_app(
|
|
500
271
|
compile: bool = False,
|
|
501
272
|
check_if_schema_up_to_date: bool = False,
|
|
@@ -509,12 +280,12 @@ def compile_or_validate_app(
|
|
|
509
280
|
prerender_routes: Whether to prerender routes.
|
|
510
281
|
"""
|
|
511
282
|
if compile:
|
|
512
|
-
|
|
283
|
+
get_compiled_app(
|
|
513
284
|
check_if_schema_up_to_date=check_if_schema_up_to_date,
|
|
514
285
|
prerender_routes=prerender_routes,
|
|
515
286
|
)
|
|
516
287
|
else:
|
|
517
|
-
|
|
288
|
+
get_and_validate_app(check_if_schema_up_to_date=check_if_schema_up_to_date)
|
|
518
289
|
|
|
519
290
|
|
|
520
291
|
def get_redis() -> Redis | None:
|
|
@@ -573,9 +344,9 @@ async def get_redis_status() -> dict[str, bool | None]:
|
|
|
573
344
|
"""
|
|
574
345
|
try:
|
|
575
346
|
status = True
|
|
576
|
-
redis_client =
|
|
347
|
+
redis_client = get_redis()
|
|
577
348
|
if redis_client is not None:
|
|
578
|
-
redis_client.ping()
|
|
349
|
+
await redis_client.ping()
|
|
579
350
|
else:
|
|
580
351
|
status = None
|
|
581
352
|
except RedisError:
|
|
@@ -616,321 +387,6 @@ def validate_app_name(app_name: str | None = None) -> str:
|
|
|
616
387
|
return app_name
|
|
617
388
|
|
|
618
389
|
|
|
619
|
-
def rename_path_up_tree(full_path: str | Path, old_name: str, new_name: str) -> Path:
|
|
620
|
-
"""Rename all instances of `old_name` in the path (file and directories) to `new_name`.
|
|
621
|
-
The renaming stops when we reach the directory containing `rxconfig.py`.
|
|
622
|
-
|
|
623
|
-
Args:
|
|
624
|
-
full_path: The full path to start renaming from.
|
|
625
|
-
old_name: The name to be replaced.
|
|
626
|
-
new_name: The replacement name.
|
|
627
|
-
|
|
628
|
-
Returns:
|
|
629
|
-
The updated path after renaming.
|
|
630
|
-
"""
|
|
631
|
-
current_path = Path(full_path)
|
|
632
|
-
new_path = None
|
|
633
|
-
|
|
634
|
-
while True:
|
|
635
|
-
directory, base = current_path.parent, current_path.name
|
|
636
|
-
# Stop renaming when we reach the root dir (which contains rxconfig.py)
|
|
637
|
-
if current_path.is_dir() and (current_path / "rxconfig.py").exists():
|
|
638
|
-
new_path = current_path
|
|
639
|
-
break
|
|
640
|
-
|
|
641
|
-
if old_name == base.removesuffix(constants.Ext.PY):
|
|
642
|
-
new_base = base.replace(old_name, new_name)
|
|
643
|
-
new_path = directory / new_base
|
|
644
|
-
current_path.rename(new_path)
|
|
645
|
-
console.debug(f"Renamed {current_path} -> {new_path}")
|
|
646
|
-
current_path = new_path
|
|
647
|
-
else:
|
|
648
|
-
new_path = current_path
|
|
649
|
-
|
|
650
|
-
# Move up the directory tree
|
|
651
|
-
current_path = directory
|
|
652
|
-
|
|
653
|
-
return new_path
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
def rename_app(new_app_name: str, loglevel: constants.LogLevel):
|
|
657
|
-
"""Rename the app directory.
|
|
658
|
-
|
|
659
|
-
Args:
|
|
660
|
-
new_app_name: The new name for the app.
|
|
661
|
-
loglevel: The log level to use.
|
|
662
|
-
|
|
663
|
-
Raises:
|
|
664
|
-
Exit: If the command is not ran in the root dir or the app module cannot be imported.
|
|
665
|
-
"""
|
|
666
|
-
# Set the log level.
|
|
667
|
-
console.set_log_level(loglevel)
|
|
668
|
-
|
|
669
|
-
if not constants.Config.FILE.exists():
|
|
670
|
-
console.error(
|
|
671
|
-
"No rxconfig.py found. Make sure you are in the root directory of your app."
|
|
672
|
-
)
|
|
673
|
-
raise click.exceptions.Exit(1)
|
|
674
|
-
|
|
675
|
-
sys.path.insert(0, str(Path.cwd()))
|
|
676
|
-
|
|
677
|
-
config = get_config()
|
|
678
|
-
module_path = get_module_path(config.module)
|
|
679
|
-
if module_path is None:
|
|
680
|
-
console.error(f"Could not find module {config.module}.")
|
|
681
|
-
raise click.exceptions.Exit(1)
|
|
682
|
-
|
|
683
|
-
console.info(f"Renaming app directory to {new_app_name}.")
|
|
684
|
-
process_directory(
|
|
685
|
-
Path.cwd(),
|
|
686
|
-
config.app_name,
|
|
687
|
-
new_app_name,
|
|
688
|
-
exclude_dirs=[constants.Dirs.WEB, constants.Dirs.APP_ASSETS],
|
|
689
|
-
)
|
|
690
|
-
|
|
691
|
-
rename_path_up_tree(module_path, config.app_name, new_app_name)
|
|
692
|
-
|
|
693
|
-
console.success(f"App directory renamed to [bold]{new_app_name}[/bold].")
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
def rename_imports_and_app_name(file_path: str | Path, old_name: str, new_name: str):
|
|
697
|
-
"""Rename imports the file using string replacement as well as app_name in rxconfig.py.
|
|
698
|
-
|
|
699
|
-
Args:
|
|
700
|
-
file_path: The file to process.
|
|
701
|
-
old_name: The old name to replace.
|
|
702
|
-
new_name: The new name to use.
|
|
703
|
-
"""
|
|
704
|
-
file_path = Path(file_path)
|
|
705
|
-
content = file_path.read_text()
|
|
706
|
-
|
|
707
|
-
# Replace `from old_name.` or `from old_name` with `from new_name`
|
|
708
|
-
content = re.sub(
|
|
709
|
-
rf"\bfrom {re.escape(old_name)}(\b|\.|\s)",
|
|
710
|
-
lambda match: f"from {new_name}{match.group(1)}",
|
|
711
|
-
content,
|
|
712
|
-
)
|
|
713
|
-
|
|
714
|
-
# Replace `import old_name` with `import new_name`
|
|
715
|
-
content = re.sub(
|
|
716
|
-
rf"\bimport {re.escape(old_name)}\b",
|
|
717
|
-
f"import {new_name}",
|
|
718
|
-
content,
|
|
719
|
-
)
|
|
720
|
-
|
|
721
|
-
# Replace `app_name="old_name"` in rx.Config
|
|
722
|
-
content = re.sub(
|
|
723
|
-
rf'\bapp_name\s*=\s*["\']{re.escape(old_name)}["\']',
|
|
724
|
-
f'app_name="{new_name}"',
|
|
725
|
-
content,
|
|
726
|
-
)
|
|
727
|
-
|
|
728
|
-
# Replace positional argument `"old_name"` in rx.Config
|
|
729
|
-
content = re.sub(
|
|
730
|
-
rf'\brx\.Config\(\s*["\']{re.escape(old_name)}["\']',
|
|
731
|
-
f'rx.Config("{new_name}"',
|
|
732
|
-
content,
|
|
733
|
-
)
|
|
734
|
-
|
|
735
|
-
file_path.write_text(content)
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
def process_directory(
|
|
739
|
-
directory: str | Path,
|
|
740
|
-
old_name: str,
|
|
741
|
-
new_name: str,
|
|
742
|
-
exclude_dirs: list | None = None,
|
|
743
|
-
extensions: list | None = None,
|
|
744
|
-
):
|
|
745
|
-
"""Process files with specified extensions in a directory, excluding specified directories.
|
|
746
|
-
|
|
747
|
-
Args:
|
|
748
|
-
directory: The root directory to process.
|
|
749
|
-
old_name: The old name to replace.
|
|
750
|
-
new_name: The new name to use.
|
|
751
|
-
exclude_dirs: List of directory names to exclude. Defaults to None.
|
|
752
|
-
extensions: List of file extensions to process.
|
|
753
|
-
"""
|
|
754
|
-
exclude_dirs = exclude_dirs or []
|
|
755
|
-
extensions = extensions or [
|
|
756
|
-
constants.Ext.PY,
|
|
757
|
-
constants.Ext.MD,
|
|
758
|
-
] # include .md files, typically used in reflex-web.
|
|
759
|
-
extensions_set = {ext.lstrip(".") for ext in extensions}
|
|
760
|
-
directory = Path(directory)
|
|
761
|
-
|
|
762
|
-
root_exclude_dirs = {directory / exclude_dir for exclude_dir in exclude_dirs}
|
|
763
|
-
|
|
764
|
-
files = (
|
|
765
|
-
p.resolve()
|
|
766
|
-
for p in directory.glob("**/*")
|
|
767
|
-
if p.is_file() and p.suffix.lstrip(".") in extensions_set
|
|
768
|
-
)
|
|
769
|
-
|
|
770
|
-
for file_path in files:
|
|
771
|
-
if not any(
|
|
772
|
-
file_path.is_relative_to(exclude_dir) for exclude_dir in root_exclude_dirs
|
|
773
|
-
):
|
|
774
|
-
rename_imports_and_app_name(file_path, old_name, new_name)
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
def create_config(app_name: str):
|
|
778
|
-
"""Create a new rxconfig file.
|
|
779
|
-
|
|
780
|
-
Args:
|
|
781
|
-
app_name: The name of the app.
|
|
782
|
-
"""
|
|
783
|
-
# Import here to avoid circular imports.
|
|
784
|
-
from reflex.compiler import templates
|
|
785
|
-
|
|
786
|
-
config_name = f"{re.sub(r'[^a-zA-Z]', '', app_name).capitalize()}Config"
|
|
787
|
-
|
|
788
|
-
console.debug(f"Creating {constants.Config.FILE}")
|
|
789
|
-
constants.Config.FILE.write_text(
|
|
790
|
-
templates.RXCONFIG.render(app_name=app_name, config_name=config_name)
|
|
791
|
-
)
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
def initialize_gitignore(
|
|
795
|
-
gitignore_file: Path = constants.GitIgnore.FILE,
|
|
796
|
-
files_to_ignore: set[str] | list[str] = constants.GitIgnore.DEFAULTS,
|
|
797
|
-
):
|
|
798
|
-
"""Initialize the template .gitignore file.
|
|
799
|
-
|
|
800
|
-
Args:
|
|
801
|
-
gitignore_file: The .gitignore file to create.
|
|
802
|
-
files_to_ignore: The files to add to the .gitignore file.
|
|
803
|
-
"""
|
|
804
|
-
# Combine with the current ignored files.
|
|
805
|
-
current_ignore: list[str] = []
|
|
806
|
-
if gitignore_file.exists():
|
|
807
|
-
current_ignore = [ln.strip() for ln in gitignore_file.read_text().splitlines()]
|
|
808
|
-
|
|
809
|
-
if files_to_ignore == current_ignore:
|
|
810
|
-
console.debug(f"{gitignore_file} already up to date.")
|
|
811
|
-
return
|
|
812
|
-
files_to_ignore = [ln for ln in files_to_ignore if ln not in current_ignore]
|
|
813
|
-
files_to_ignore += current_ignore
|
|
814
|
-
|
|
815
|
-
# Write files to the .gitignore file.
|
|
816
|
-
gitignore_file.touch(exist_ok=True)
|
|
817
|
-
console.debug(f"Creating {gitignore_file}")
|
|
818
|
-
gitignore_file.write_text("\n".join(files_to_ignore) + "\n")
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
def initialize_requirements_txt() -> bool:
|
|
822
|
-
"""Initialize the requirements.txt file.
|
|
823
|
-
If absent and no pyproject.toml file exists, generate one for the user.
|
|
824
|
-
If the requirements.txt does not have reflex as dependency,
|
|
825
|
-
generate a requirement pinning current version and append to
|
|
826
|
-
the requirements.txt file.
|
|
827
|
-
|
|
828
|
-
Returns:
|
|
829
|
-
True if the user has to update the requirements.txt file.
|
|
830
|
-
|
|
831
|
-
Raises:
|
|
832
|
-
Exit: If the requirements.txt file cannot be read or written to.
|
|
833
|
-
"""
|
|
834
|
-
requirements_file_path = Path(constants.RequirementsTxt.FILE)
|
|
835
|
-
if (
|
|
836
|
-
not requirements_file_path.exists()
|
|
837
|
-
and Path(constants.PyprojectToml.FILE).exists()
|
|
838
|
-
):
|
|
839
|
-
return True
|
|
840
|
-
|
|
841
|
-
requirements_file_path.touch(exist_ok=True)
|
|
842
|
-
|
|
843
|
-
for encoding in [None, "utf-8"]:
|
|
844
|
-
try:
|
|
845
|
-
content = requirements_file_path.read_text(encoding)
|
|
846
|
-
break
|
|
847
|
-
except UnicodeDecodeError:
|
|
848
|
-
continue
|
|
849
|
-
except Exception as e:
|
|
850
|
-
console.error(f"Failed to read {requirements_file_path}.")
|
|
851
|
-
raise click.exceptions.Exit(1) from e
|
|
852
|
-
else:
|
|
853
|
-
return True
|
|
854
|
-
|
|
855
|
-
for line in content.splitlines():
|
|
856
|
-
if re.match(r"^reflex[^a-zA-Z0-9]", line):
|
|
857
|
-
console.debug(f"{requirements_file_path} already has reflex as dependency.")
|
|
858
|
-
return False
|
|
859
|
-
|
|
860
|
-
console.debug(
|
|
861
|
-
f"Appending {constants.RequirementsTxt.DEFAULTS_STUB} to {requirements_file_path}"
|
|
862
|
-
)
|
|
863
|
-
with requirements_file_path.open("a", encoding=encoding) as f:
|
|
864
|
-
f.write(
|
|
865
|
-
"\n" + constants.RequirementsTxt.DEFAULTS_STUB + constants.Reflex.VERSION
|
|
866
|
-
)
|
|
867
|
-
|
|
868
|
-
return False
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
def initialize_app_directory(
|
|
872
|
-
app_name: str,
|
|
873
|
-
template_name: str = constants.Templates.DEFAULT,
|
|
874
|
-
template_code_dir_name: str | None = None,
|
|
875
|
-
template_dir: Path | None = None,
|
|
876
|
-
):
|
|
877
|
-
"""Initialize the app directory on reflex init.
|
|
878
|
-
|
|
879
|
-
Args:
|
|
880
|
-
app_name: The name of the app.
|
|
881
|
-
template_name: The name of the template to use.
|
|
882
|
-
template_code_dir_name: The name of the code directory in the template.
|
|
883
|
-
template_dir: The directory of the template source files.
|
|
884
|
-
|
|
885
|
-
Raises:
|
|
886
|
-
Exit: If template_name, template_code_dir_name, template_dir combination is not supported.
|
|
887
|
-
"""
|
|
888
|
-
console.log("Initializing the app directory.")
|
|
889
|
-
|
|
890
|
-
# By default, use the blank template from local assets.
|
|
891
|
-
if template_name == constants.Templates.DEFAULT:
|
|
892
|
-
if template_code_dir_name is not None or template_dir is not None:
|
|
893
|
-
console.error(
|
|
894
|
-
f"Only {template_name=} should be provided, got {template_code_dir_name=}, {template_dir=}."
|
|
895
|
-
)
|
|
896
|
-
raise click.exceptions.Exit(1)
|
|
897
|
-
template_code_dir_name = constants.Templates.Dirs.CODE
|
|
898
|
-
template_dir = Path(constants.Templates.Dirs.BASE, "apps", template_name)
|
|
899
|
-
else:
|
|
900
|
-
if template_code_dir_name is None or template_dir is None:
|
|
901
|
-
console.error(
|
|
902
|
-
f"For `{template_name}` template, `template_code_dir_name` and `template_dir` should both be provided."
|
|
903
|
-
)
|
|
904
|
-
raise click.exceptions.Exit(1)
|
|
905
|
-
|
|
906
|
-
console.debug(f"Using {template_name=} {template_dir=} {template_code_dir_name=}.")
|
|
907
|
-
|
|
908
|
-
# Remove __pycache__ dirs in template directory and current directory.
|
|
909
|
-
for pycache_dir in [
|
|
910
|
-
*template_dir.glob("**/__pycache__"),
|
|
911
|
-
*Path.cwd().glob("**/__pycache__"),
|
|
912
|
-
]:
|
|
913
|
-
shutil.rmtree(pycache_dir, ignore_errors=True)
|
|
914
|
-
|
|
915
|
-
for file in template_dir.iterdir():
|
|
916
|
-
# Copy the file to current directory but keep the name the same.
|
|
917
|
-
path_ops.cp(str(file), file.name)
|
|
918
|
-
|
|
919
|
-
# Rename the template app to the app name.
|
|
920
|
-
path_ops.mv(template_code_dir_name, app_name)
|
|
921
|
-
path_ops.mv(
|
|
922
|
-
Path(app_name) / (template_name + constants.Ext.PY),
|
|
923
|
-
Path(app_name) / (app_name + constants.Ext.PY),
|
|
924
|
-
)
|
|
925
|
-
|
|
926
|
-
# Fix up the imports.
|
|
927
|
-
path_ops.find_replace(
|
|
928
|
-
app_name,
|
|
929
|
-
f"from {template_name}",
|
|
930
|
-
f"from {app_name}",
|
|
931
|
-
)
|
|
932
|
-
|
|
933
|
-
|
|
934
390
|
def get_project_hash(raise_on_fail: bool = False) -> int | None:
|
|
935
391
|
"""Get the project hash from the reflex.json file if the file exists.
|
|
936
392
|
|
|
@@ -947,334 +403,6 @@ def get_project_hash(raise_on_fail: bool = False) -> int | None:
|
|
|
947
403
|
return data.get("project_hash")
|
|
948
404
|
|
|
949
405
|
|
|
950
|
-
def initialize_web_directory():
|
|
951
|
-
"""Initialize the web directory on reflex init."""
|
|
952
|
-
console.log("Initializing the web directory.")
|
|
953
|
-
|
|
954
|
-
# Reuse the hash if one is already created, so we don't over-write it when running reflex init
|
|
955
|
-
project_hash = get_project_hash()
|
|
956
|
-
|
|
957
|
-
console.debug(f"Copying {constants.Templates.Dirs.WEB_TEMPLATE} to {get_web_dir()}")
|
|
958
|
-
path_ops.copy_tree(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
|
|
959
|
-
|
|
960
|
-
console.debug("Initializing the web directory.")
|
|
961
|
-
initialize_package_json()
|
|
962
|
-
|
|
963
|
-
console.debug("Initializing the bun config file.")
|
|
964
|
-
initialize_bun_config()
|
|
965
|
-
|
|
966
|
-
console.debug("Initializing the .npmrc file.")
|
|
967
|
-
initialize_npmrc()
|
|
968
|
-
|
|
969
|
-
console.debug("Initializing the public directory.")
|
|
970
|
-
path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
|
|
971
|
-
|
|
972
|
-
console.debug("Initializing the react-router.config.js file.")
|
|
973
|
-
update_react_router_config()
|
|
974
|
-
|
|
975
|
-
console.debug("Initializing the vite.config.js file.")
|
|
976
|
-
initialize_vite_config()
|
|
977
|
-
|
|
978
|
-
console.debug("Initializing the reflex.json file.")
|
|
979
|
-
# Initialize the reflex json file.
|
|
980
|
-
init_reflex_json(project_hash=project_hash)
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
def _compile_package_json():
|
|
984
|
-
return templates.PACKAGE_JSON.render(
|
|
985
|
-
scripts={
|
|
986
|
-
"dev": constants.PackageJson.Commands.DEV,
|
|
987
|
-
"export": constants.PackageJson.Commands.EXPORT,
|
|
988
|
-
"prod": constants.PackageJson.Commands.PROD,
|
|
989
|
-
},
|
|
990
|
-
dependencies=constants.PackageJson.DEPENDENCIES,
|
|
991
|
-
dev_dependencies=constants.PackageJson.DEV_DEPENDENCIES,
|
|
992
|
-
overrides=constants.PackageJson.OVERRIDES,
|
|
993
|
-
)
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
def initialize_package_json():
|
|
997
|
-
"""Render and write in .web the package.json file."""
|
|
998
|
-
output_path = get_web_dir() / constants.PackageJson.PATH
|
|
999
|
-
output_path.write_text(_compile_package_json())
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
def _compile_vite_config(config: Config):
|
|
1003
|
-
# base must have exactly one trailing slash
|
|
1004
|
-
base = "/"
|
|
1005
|
-
if frontend_path := config.frontend_path.strip("/"):
|
|
1006
|
-
base += frontend_path + "/"
|
|
1007
|
-
return templates.VITE_CONFIG.render(base=base)
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
def initialize_vite_config():
|
|
1011
|
-
"""Render and write in .web the vite.config.js file using Reflex config."""
|
|
1012
|
-
vite_config_file_path = get_web_dir() / constants.ReactRouter.VITE_CONFIG_FILE
|
|
1013
|
-
vite_config_file_path.write_text(_compile_vite_config(get_config()))
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
def initialize_bun_config():
|
|
1017
|
-
"""Initialize the bun config file."""
|
|
1018
|
-
bun_config_path = get_web_dir() / constants.Bun.CONFIG_PATH
|
|
1019
|
-
|
|
1020
|
-
if (custom_bunfig := Path(constants.Bun.CONFIG_PATH)).exists():
|
|
1021
|
-
bunfig_content = custom_bunfig.read_text()
|
|
1022
|
-
console.info(f"Copying custom bunfig.toml inside {get_web_dir()} folder")
|
|
1023
|
-
else:
|
|
1024
|
-
best_registry = get_npm_registry()
|
|
1025
|
-
bunfig_content = constants.Bun.DEFAULT_CONFIG.format(registry=best_registry)
|
|
1026
|
-
|
|
1027
|
-
bun_config_path.write_text(bunfig_content)
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
def initialize_npmrc():
|
|
1031
|
-
"""Initialize the .npmrc file."""
|
|
1032
|
-
npmrc_path = get_web_dir() / constants.Node.CONFIG_PATH
|
|
1033
|
-
|
|
1034
|
-
if (custom_npmrc := Path(constants.Node.CONFIG_PATH)).exists():
|
|
1035
|
-
npmrc_content = custom_npmrc.read_text()
|
|
1036
|
-
console.info(f"Copying custom .npmrc inside {get_web_dir()} folder")
|
|
1037
|
-
else:
|
|
1038
|
-
best_registry = get_npm_registry()
|
|
1039
|
-
npmrc_content = constants.Node.DEFAULT_CONFIG.format(registry=best_registry)
|
|
1040
|
-
|
|
1041
|
-
npmrc_path.write_text(npmrc_content)
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
def init_reflex_json(project_hash: int | None):
|
|
1045
|
-
"""Write the hash of the Reflex project to a REFLEX_JSON.
|
|
1046
|
-
|
|
1047
|
-
Reuse the hash if one is already created, therefore do not
|
|
1048
|
-
overwrite it every time we run the reflex init command
|
|
1049
|
-
.
|
|
1050
|
-
|
|
1051
|
-
Args:
|
|
1052
|
-
project_hash: The app hash.
|
|
1053
|
-
"""
|
|
1054
|
-
if project_hash is not None:
|
|
1055
|
-
console.debug(f"Project hash is already set to {project_hash}.")
|
|
1056
|
-
else:
|
|
1057
|
-
# Get a random project hash.
|
|
1058
|
-
project_hash = random.getrandbits(128)
|
|
1059
|
-
console.debug(f"Setting project hash to {project_hash}.")
|
|
1060
|
-
|
|
1061
|
-
# Write the hash and version to the reflex json file.
|
|
1062
|
-
reflex_json = {
|
|
1063
|
-
"version": constants.Reflex.VERSION,
|
|
1064
|
-
"project_hash": project_hash,
|
|
1065
|
-
}
|
|
1066
|
-
path_ops.update_json_file(get_web_dir() / constants.Reflex.JSON, reflex_json)
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
def update_react_router_config(prerender_routes: bool = False):
|
|
1070
|
-
"""Update react-router.config.js config from Reflex config.
|
|
1071
|
-
|
|
1072
|
-
Args:
|
|
1073
|
-
prerender_routes: Whether to enable prerendering of routes.
|
|
1074
|
-
"""
|
|
1075
|
-
react_router_config_file_path = get_web_dir() / constants.ReactRouter.CONFIG_FILE
|
|
1076
|
-
|
|
1077
|
-
new_react_router_config = _update_react_router_config(
|
|
1078
|
-
get_config(), prerender_routes=prerender_routes
|
|
1079
|
-
)
|
|
1080
|
-
|
|
1081
|
-
# Overwriting the config file triggers a full server reload, so make sure
|
|
1082
|
-
# there is actually a diff.
|
|
1083
|
-
old_react_router_config = (
|
|
1084
|
-
react_router_config_file_path.read_text()
|
|
1085
|
-
if react_router_config_file_path.exists()
|
|
1086
|
-
else ""
|
|
1087
|
-
)
|
|
1088
|
-
if old_react_router_config != new_react_router_config:
|
|
1089
|
-
react_router_config_file_path.write_text(new_react_router_config)
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
def _update_react_router_config(config: Config, prerender_routes: bool = False):
|
|
1093
|
-
react_router_config = {
|
|
1094
|
-
"basename": "/" + (config.frontend_path or "").removeprefix("/"),
|
|
1095
|
-
"future": {
|
|
1096
|
-
"unstable_optimizeDeps": True,
|
|
1097
|
-
},
|
|
1098
|
-
"ssr": False,
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
if prerender_routes:
|
|
1102
|
-
react_router_config["prerender"] = True
|
|
1103
|
-
react_router_config["build"] = constants.Dirs.BUILD_DIR
|
|
1104
|
-
|
|
1105
|
-
return f"export default {json.dumps(react_router_config)};"
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
def remove_existing_bun_installation():
|
|
1109
|
-
"""Remove existing bun installation."""
|
|
1110
|
-
console.debug("Removing existing bun installation.")
|
|
1111
|
-
if Path(get_config().bun_path).exists():
|
|
1112
|
-
path_ops.rm(constants.Bun.ROOT_PATH)
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
def download_and_run(url: str, *args, show_status: bool = False, **env):
|
|
1116
|
-
"""Download and run a script.
|
|
1117
|
-
|
|
1118
|
-
Args:
|
|
1119
|
-
url: The url of the script.
|
|
1120
|
-
args: The arguments to pass to the script.
|
|
1121
|
-
show_status: Whether to show the status of the script.
|
|
1122
|
-
env: The environment variables to use.
|
|
1123
|
-
|
|
1124
|
-
Raises:
|
|
1125
|
-
Exit: If the script fails to download.
|
|
1126
|
-
"""
|
|
1127
|
-
import httpx
|
|
1128
|
-
|
|
1129
|
-
# Download the script
|
|
1130
|
-
console.debug(f"Downloading {url}")
|
|
1131
|
-
try:
|
|
1132
|
-
response = net.get(url)
|
|
1133
|
-
response.raise_for_status()
|
|
1134
|
-
except httpx.HTTPError as e:
|
|
1135
|
-
console.error(
|
|
1136
|
-
f"Failed to download bun install script. You can install or update bun manually from https://bun.com \n{e}"
|
|
1137
|
-
)
|
|
1138
|
-
raise click.exceptions.Exit(1) from None
|
|
1139
|
-
|
|
1140
|
-
# Save the script to a temporary file.
|
|
1141
|
-
with tempfile.NamedTemporaryFile() as tempfile_file:
|
|
1142
|
-
script = Path(tempfile_file.name)
|
|
1143
|
-
|
|
1144
|
-
script.write_text(response.text)
|
|
1145
|
-
|
|
1146
|
-
# Run the script.
|
|
1147
|
-
env = {**os.environ, **env}
|
|
1148
|
-
process = processes.new_process(["bash", str(script), *args], env=env)
|
|
1149
|
-
show = processes.show_status if show_status else processes.show_logs
|
|
1150
|
-
show(f"Installing {url}", process)
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
def install_bun():
|
|
1154
|
-
"""Install bun onto the user's system.
|
|
1155
|
-
|
|
1156
|
-
Raises:
|
|
1157
|
-
SystemPackageMissingError: If "unzip" is missing.
|
|
1158
|
-
"""
|
|
1159
|
-
one_drive_in_path = windows_check_onedrive_in_path()
|
|
1160
|
-
if constants.IS_WINDOWS and one_drive_in_path:
|
|
1161
|
-
console.warn(
|
|
1162
|
-
"Creating project directories in OneDrive is not recommended for bun usage on windows. This will fallback to npm."
|
|
1163
|
-
)
|
|
1164
|
-
|
|
1165
|
-
bun_path = path_ops.get_bun_path()
|
|
1166
|
-
|
|
1167
|
-
# Skip if bun is already installed.
|
|
1168
|
-
if (
|
|
1169
|
-
bun_path
|
|
1170
|
-
and (current_version := get_bun_version(bun_path=bun_path))
|
|
1171
|
-
and current_version >= version.parse(constants.Bun.MIN_VERSION)
|
|
1172
|
-
):
|
|
1173
|
-
console.debug("Skipping bun installation as it is already installed.")
|
|
1174
|
-
return
|
|
1175
|
-
|
|
1176
|
-
if bun_path and path_ops.use_system_bun():
|
|
1177
|
-
validate_bun(bun_path=bun_path)
|
|
1178
|
-
return
|
|
1179
|
-
|
|
1180
|
-
# if unzip is installed
|
|
1181
|
-
if constants.IS_WINDOWS:
|
|
1182
|
-
processes.new_process(
|
|
1183
|
-
[
|
|
1184
|
-
"powershell",
|
|
1185
|
-
"-c",
|
|
1186
|
-
f"irm {constants.Bun.WINDOWS_INSTALL_URL}|iex",
|
|
1187
|
-
],
|
|
1188
|
-
env={
|
|
1189
|
-
"BUN_INSTALL": str(constants.Bun.ROOT_PATH),
|
|
1190
|
-
"BUN_VERSION": constants.Bun.VERSION,
|
|
1191
|
-
},
|
|
1192
|
-
shell=True,
|
|
1193
|
-
run=True,
|
|
1194
|
-
show_logs=console.is_debug(),
|
|
1195
|
-
)
|
|
1196
|
-
else:
|
|
1197
|
-
if path_ops.which("unzip") is None:
|
|
1198
|
-
msg = "unzip"
|
|
1199
|
-
raise SystemPackageMissingError(msg)
|
|
1200
|
-
|
|
1201
|
-
# Run the bun install script.
|
|
1202
|
-
download_and_run(
|
|
1203
|
-
constants.Bun.INSTALL_URL,
|
|
1204
|
-
f"bun-v{constants.Bun.VERSION}",
|
|
1205
|
-
BUN_INSTALL=str(constants.Bun.ROOT_PATH),
|
|
1206
|
-
BUN_VERSION=str(constants.Bun.VERSION),
|
|
1207
|
-
)
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
@cached_procedure(
|
|
1211
|
-
cache_file_path=lambda: get_web_dir() / "reflex.install_frontend_packages.cached",
|
|
1212
|
-
payload_fn=lambda packages, config: f"{sorted(packages)!r},{config.json()}",
|
|
1213
|
-
)
|
|
1214
|
-
def install_frontend_packages(packages: set[str], config: Config):
|
|
1215
|
-
"""Installs the base and custom frontend packages.
|
|
1216
|
-
|
|
1217
|
-
Args:
|
|
1218
|
-
packages: A list of package names to be installed.
|
|
1219
|
-
config: The config object.
|
|
1220
|
-
|
|
1221
|
-
Example:
|
|
1222
|
-
>>> install_frontend_packages(["react", "react-dom"], get_config())
|
|
1223
|
-
"""
|
|
1224
|
-
install_package_managers = get_nodejs_compatible_package_managers(
|
|
1225
|
-
raise_on_none=True
|
|
1226
|
-
)
|
|
1227
|
-
|
|
1228
|
-
env = (
|
|
1229
|
-
{
|
|
1230
|
-
"NODE_TLS_REJECT_UNAUTHORIZED": "0",
|
|
1231
|
-
}
|
|
1232
|
-
if environment.SSL_NO_VERIFY.get()
|
|
1233
|
-
else {}
|
|
1234
|
-
)
|
|
1235
|
-
|
|
1236
|
-
primary_package_manager = install_package_managers[0]
|
|
1237
|
-
fallbacks = install_package_managers[1:]
|
|
1238
|
-
|
|
1239
|
-
run_package_manager = functools.partial(
|
|
1240
|
-
processes.run_process_with_fallbacks,
|
|
1241
|
-
fallbacks=fallbacks,
|
|
1242
|
-
analytics_enabled=True,
|
|
1243
|
-
cwd=get_web_dir(),
|
|
1244
|
-
shell=constants.IS_WINDOWS,
|
|
1245
|
-
env=env,
|
|
1246
|
-
)
|
|
1247
|
-
|
|
1248
|
-
run_package_manager(
|
|
1249
|
-
[primary_package_manager, "install", "--legacy-peer-deps"],
|
|
1250
|
-
show_status_message="Installing base frontend packages",
|
|
1251
|
-
)
|
|
1252
|
-
|
|
1253
|
-
development_deps: set[str] = set()
|
|
1254
|
-
for plugin in config.plugins:
|
|
1255
|
-
development_deps.update(plugin.get_frontend_development_dependencies())
|
|
1256
|
-
packages.update(plugin.get_frontend_dependencies())
|
|
1257
|
-
|
|
1258
|
-
if development_deps:
|
|
1259
|
-
run_package_manager(
|
|
1260
|
-
[
|
|
1261
|
-
primary_package_manager,
|
|
1262
|
-
"add",
|
|
1263
|
-
"--legacy-peer-deps",
|
|
1264
|
-
"-d",
|
|
1265
|
-
*development_deps,
|
|
1266
|
-
],
|
|
1267
|
-
show_status_message="Installing frontend development dependencies",
|
|
1268
|
-
)
|
|
1269
|
-
|
|
1270
|
-
# Install custom packages defined in frontend_packages
|
|
1271
|
-
if packages:
|
|
1272
|
-
run_package_manager(
|
|
1273
|
-
[primary_package_manager, "add", "--legacy-peer-deps", *packages],
|
|
1274
|
-
show_status_message="Installing frontend packages from config and components",
|
|
1275
|
-
)
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
406
|
def check_running_mode(frontend: bool, backend: bool) -> tuple[bool, bool]:
|
|
1279
407
|
"""Check if the app is running in frontend or backend mode.
|
|
1280
408
|
|
|
@@ -1317,8 +445,7 @@ def needs_reinit() -> bool:
|
|
|
1317
445
|
if not get_web_dir().exists():
|
|
1318
446
|
return True
|
|
1319
447
|
|
|
1320
|
-
|
|
1321
|
-
if not is_latest_template():
|
|
448
|
+
if not _is_app_compiled_with_same_reflex_version():
|
|
1322
449
|
return True
|
|
1323
450
|
|
|
1324
451
|
if constants.IS_WINDOWS:
|
|
@@ -1334,12 +461,7 @@ def needs_reinit() -> bool:
|
|
|
1334
461
|
return False
|
|
1335
462
|
|
|
1336
463
|
|
|
1337
|
-
def
|
|
1338
|
-
"""Whether the app is using the latest template.
|
|
1339
|
-
|
|
1340
|
-
Returns:
|
|
1341
|
-
Whether the app is using the latest template.
|
|
1342
|
-
"""
|
|
464
|
+
def _is_app_compiled_with_same_reflex_version() -> bool:
|
|
1343
465
|
json_file = get_web_dir() / constants.Reflex.JSON
|
|
1344
466
|
if not json_file.exists():
|
|
1345
467
|
return False
|
|
@@ -1347,59 +469,6 @@ def is_latest_template() -> bool:
|
|
|
1347
469
|
return app_version == constants.Reflex.VERSION
|
|
1348
470
|
|
|
1349
471
|
|
|
1350
|
-
def validate_bun(bun_path: Path | None = None):
|
|
1351
|
-
"""Validate bun if a custom bun path is specified to ensure the bun version meets requirements.
|
|
1352
|
-
|
|
1353
|
-
Args:
|
|
1354
|
-
bun_path: The path to the bun executable. If None, the default bun path is used.
|
|
1355
|
-
|
|
1356
|
-
Raises:
|
|
1357
|
-
Exit: If custom specified bun does not exist or does not meet requirements.
|
|
1358
|
-
"""
|
|
1359
|
-
bun_path = bun_path or path_ops.get_bun_path()
|
|
1360
|
-
|
|
1361
|
-
if bun_path is None:
|
|
1362
|
-
return
|
|
1363
|
-
|
|
1364
|
-
if not path_ops.samefile(bun_path, constants.Bun.DEFAULT_PATH):
|
|
1365
|
-
console.info(f"Using custom Bun path: {bun_path}")
|
|
1366
|
-
bun_version = get_bun_version()
|
|
1367
|
-
if bun_version is None:
|
|
1368
|
-
console.error(
|
|
1369
|
-
"Failed to obtain bun version. Make sure the specified bun path in your config is correct."
|
|
1370
|
-
)
|
|
1371
|
-
raise click.exceptions.Exit(1)
|
|
1372
|
-
if bun_version < version.parse(constants.Bun.MIN_VERSION):
|
|
1373
|
-
console.warn(
|
|
1374
|
-
f"Reflex requires bun version {constants.Bun.MIN_VERSION} or higher to run, but the detected version is "
|
|
1375
|
-
f"{bun_version}. If you have specified a custom bun path in your config, make sure to provide one "
|
|
1376
|
-
f"that satisfies the minimum version requirement. You can upgrade bun by running [bold]bun upgrade[/bold]."
|
|
1377
|
-
)
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
def validate_frontend_dependencies(init: bool = True):
|
|
1381
|
-
"""Validate frontend dependencies to ensure they meet requirements.
|
|
1382
|
-
|
|
1383
|
-
Args:
|
|
1384
|
-
init: whether running `reflex init`
|
|
1385
|
-
|
|
1386
|
-
Raises:
|
|
1387
|
-
Exit: If the package manager is invalid.
|
|
1388
|
-
"""
|
|
1389
|
-
if not init:
|
|
1390
|
-
try:
|
|
1391
|
-
get_js_package_executor(raise_on_none=True)
|
|
1392
|
-
except FileNotFoundError as e:
|
|
1393
|
-
raise click.exceptions.Exit(1) from e
|
|
1394
|
-
|
|
1395
|
-
if prefer_npm_over_bun() and not check_node_version():
|
|
1396
|
-
node_version = get_node_version()
|
|
1397
|
-
console.error(
|
|
1398
|
-
f"Reflex requires node version {constants.Node.MIN_VERSION} or higher to run, but the detected version is {node_version}",
|
|
1399
|
-
)
|
|
1400
|
-
raise click.exceptions.Exit(1)
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
472
|
def ensure_reflex_installation_id() -> int | None:
|
|
1404
473
|
"""Ensures that a reflex distinct id has been generated and stored in the reflex directory.
|
|
1405
474
|
|
|
@@ -1441,6 +510,9 @@ def initialize_reflex_user_directory():
|
|
|
1441
510
|
|
|
1442
511
|
def initialize_frontend_dependencies():
|
|
1443
512
|
"""Initialize all the frontend dependencies."""
|
|
513
|
+
from reflex.utils.frontend_skeleton import initialize_web_directory
|
|
514
|
+
from reflex.utils.js_runtimes import install_bun, validate_frontend_dependencies
|
|
515
|
+
|
|
1444
516
|
# validate dependencies before install
|
|
1445
517
|
console.debug("Validating frontend dependencies.")
|
|
1446
518
|
validate_frontend_dependencies()
|
|
@@ -1507,444 +579,6 @@ def check_schema_up_to_date():
|
|
|
1507
579
|
)
|
|
1508
580
|
|
|
1509
581
|
|
|
1510
|
-
def prompt_for_template_options(templates: list[Template]) -> str:
|
|
1511
|
-
"""Prompt the user to specify a template.
|
|
1512
|
-
|
|
1513
|
-
Args:
|
|
1514
|
-
templates: The templates to choose from.
|
|
1515
|
-
|
|
1516
|
-
Returns:
|
|
1517
|
-
The template name the user selects.
|
|
1518
|
-
|
|
1519
|
-
Raises:
|
|
1520
|
-
Exit: If the user does not select a template.
|
|
1521
|
-
"""
|
|
1522
|
-
# Show the user the URLs of each template to preview.
|
|
1523
|
-
console.print("\nGet started with a template:")
|
|
1524
|
-
|
|
1525
|
-
# Prompt the user to select a template.
|
|
1526
|
-
for index, template in enumerate(templates):
|
|
1527
|
-
console.print(f"({index}) {template.description}")
|
|
1528
|
-
|
|
1529
|
-
template = console.ask(
|
|
1530
|
-
"Which template would you like to use?",
|
|
1531
|
-
choices=[str(i) for i in range(len(templates))],
|
|
1532
|
-
show_choices=False,
|
|
1533
|
-
default="0",
|
|
1534
|
-
)
|
|
1535
|
-
|
|
1536
|
-
if not template:
|
|
1537
|
-
console.error("No template selected.")
|
|
1538
|
-
raise click.exceptions.Exit(1)
|
|
1539
|
-
|
|
1540
|
-
try:
|
|
1541
|
-
template_index = int(template)
|
|
1542
|
-
except ValueError:
|
|
1543
|
-
console.error("Invalid template selected.")
|
|
1544
|
-
raise click.exceptions.Exit(1) from None
|
|
1545
|
-
|
|
1546
|
-
if template_index < 0 or template_index >= len(templates):
|
|
1547
|
-
console.error("Invalid template selected.")
|
|
1548
|
-
raise click.exceptions.Exit(1)
|
|
1549
|
-
|
|
1550
|
-
# Return the template.
|
|
1551
|
-
return templates[template_index].name
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
def fetch_app_templates(version: str) -> dict[str, Template]:
|
|
1555
|
-
"""Fetch a dict of templates from the templates repo using github API.
|
|
1556
|
-
|
|
1557
|
-
Args:
|
|
1558
|
-
version: The version of the templates to fetch.
|
|
1559
|
-
|
|
1560
|
-
Returns:
|
|
1561
|
-
The dict of templates.
|
|
1562
|
-
"""
|
|
1563
|
-
|
|
1564
|
-
def get_release_by_tag(tag: str) -> dict | None:
|
|
1565
|
-
response = net.get(constants.Reflex.RELEASES_URL)
|
|
1566
|
-
response.raise_for_status()
|
|
1567
|
-
releases = response.json()
|
|
1568
|
-
for release in releases:
|
|
1569
|
-
if release["tag_name"] == f"v{tag}":
|
|
1570
|
-
return release
|
|
1571
|
-
return None
|
|
1572
|
-
|
|
1573
|
-
release = get_release_by_tag(version)
|
|
1574
|
-
if release is None:
|
|
1575
|
-
console.warn(f"No templates known for version {version}")
|
|
1576
|
-
return {}
|
|
1577
|
-
|
|
1578
|
-
assets = release.get("assets", [])
|
|
1579
|
-
asset = next((a for a in assets if a["name"] == "templates.json"), None)
|
|
1580
|
-
if asset is None:
|
|
1581
|
-
console.warn(f"Templates metadata not found for version {version}")
|
|
1582
|
-
return {}
|
|
1583
|
-
templates_url = asset["browser_download_url"]
|
|
1584
|
-
|
|
1585
|
-
templates_data = net.get(templates_url, follow_redirects=True).json()["templates"]
|
|
1586
|
-
|
|
1587
|
-
for template in templates_data:
|
|
1588
|
-
if template["name"] == "blank":
|
|
1589
|
-
template["code_url"] = ""
|
|
1590
|
-
continue
|
|
1591
|
-
template["code_url"] = next(
|
|
1592
|
-
(
|
|
1593
|
-
a["browser_download_url"]
|
|
1594
|
-
for a in assets
|
|
1595
|
-
if a["name"] == f"{template['name']}.zip"
|
|
1596
|
-
),
|
|
1597
|
-
None,
|
|
1598
|
-
)
|
|
1599
|
-
|
|
1600
|
-
filtered_templates = {}
|
|
1601
|
-
for tp in templates_data:
|
|
1602
|
-
if tp["hidden"] or tp["code_url"] is None:
|
|
1603
|
-
continue
|
|
1604
|
-
known_fields = {f.name for f in dataclasses.fields(Template)}
|
|
1605
|
-
filtered_templates[tp["name"]] = Template(
|
|
1606
|
-
**{k: v for k, v in tp.items() if k in known_fields}
|
|
1607
|
-
)
|
|
1608
|
-
return filtered_templates
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
def create_config_init_app_from_remote_template(app_name: str, template_url: str):
|
|
1612
|
-
"""Create new rxconfig and initialize app using a remote template.
|
|
1613
|
-
|
|
1614
|
-
Args:
|
|
1615
|
-
app_name: The name of the app.
|
|
1616
|
-
template_url: The path to the template source code as a zip file.
|
|
1617
|
-
|
|
1618
|
-
Raises:
|
|
1619
|
-
Exit: If any download, file operations fail or unexpected zip file format.
|
|
1620
|
-
|
|
1621
|
-
"""
|
|
1622
|
-
import httpx
|
|
1623
|
-
|
|
1624
|
-
# Create a temp directory for the zip download.
|
|
1625
|
-
try:
|
|
1626
|
-
temp_dir = tempfile.mkdtemp()
|
|
1627
|
-
except OSError as ose:
|
|
1628
|
-
console.error(f"Failed to create temp directory for download: {ose}")
|
|
1629
|
-
raise click.exceptions.Exit(1) from ose
|
|
1630
|
-
|
|
1631
|
-
# Use httpx GET with redirects to download the zip file.
|
|
1632
|
-
zip_file_path: Path = Path(temp_dir) / "template.zip"
|
|
1633
|
-
try:
|
|
1634
|
-
# Note: following redirects can be risky. We only allow this for reflex built templates at the moment.
|
|
1635
|
-
response = net.get(template_url, follow_redirects=True)
|
|
1636
|
-
console.debug(f"Server responded download request: {response}")
|
|
1637
|
-
response.raise_for_status()
|
|
1638
|
-
except httpx.HTTPError as he:
|
|
1639
|
-
console.error(f"Failed to download the template: {he}")
|
|
1640
|
-
raise click.exceptions.Exit(1) from he
|
|
1641
|
-
try:
|
|
1642
|
-
zip_file_path.write_bytes(response.content)
|
|
1643
|
-
console.debug(f"Downloaded the zip to {zip_file_path}")
|
|
1644
|
-
except OSError as ose:
|
|
1645
|
-
console.error(f"Unable to write the downloaded zip to disk {ose}")
|
|
1646
|
-
raise click.exceptions.Exit(1) from ose
|
|
1647
|
-
|
|
1648
|
-
# Create a temp directory for the zip extraction.
|
|
1649
|
-
try:
|
|
1650
|
-
unzip_dir = Path(tempfile.mkdtemp())
|
|
1651
|
-
except OSError as ose:
|
|
1652
|
-
console.error(f"Failed to create temp directory for extracting zip: {ose}")
|
|
1653
|
-
raise click.exceptions.Exit(1) from ose
|
|
1654
|
-
|
|
1655
|
-
try:
|
|
1656
|
-
zipfile.ZipFile(zip_file_path).extractall(path=unzip_dir)
|
|
1657
|
-
# The zip file downloaded from github looks like:
|
|
1658
|
-
# repo-name-branch/**/*, so we need to remove the top level directory.
|
|
1659
|
-
except Exception as uze:
|
|
1660
|
-
console.error(f"Failed to unzip the template: {uze}")
|
|
1661
|
-
raise click.exceptions.Exit(1) from uze
|
|
1662
|
-
|
|
1663
|
-
if len(subdirs := list(unzip_dir.iterdir())) != 1:
|
|
1664
|
-
console.error(f"Expected one directory in the zip, found {subdirs}")
|
|
1665
|
-
raise click.exceptions.Exit(1)
|
|
1666
|
-
|
|
1667
|
-
template_dir = unzip_dir / subdirs[0]
|
|
1668
|
-
console.debug(f"Template folder is located at {template_dir}")
|
|
1669
|
-
|
|
1670
|
-
# Move the rxconfig file here first.
|
|
1671
|
-
path_ops.mv(str(template_dir / constants.Config.FILE), constants.Config.FILE)
|
|
1672
|
-
new_config = get_config(reload=True)
|
|
1673
|
-
|
|
1674
|
-
# Get the template app's name from rxconfig in case it is different than
|
|
1675
|
-
# the source code repo name on github.
|
|
1676
|
-
template_name = new_config.app_name
|
|
1677
|
-
|
|
1678
|
-
create_config(app_name)
|
|
1679
|
-
initialize_app_directory(
|
|
1680
|
-
app_name,
|
|
1681
|
-
template_name=template_name,
|
|
1682
|
-
template_code_dir_name=template_name,
|
|
1683
|
-
template_dir=template_dir,
|
|
1684
|
-
)
|
|
1685
|
-
req_file = Path("requirements.txt")
|
|
1686
|
-
if req_file.exists() and len(req_file.read_text().splitlines()) > 1:
|
|
1687
|
-
console.info(
|
|
1688
|
-
"Run `pip install -r requirements.txt` to install the required python packages for this template."
|
|
1689
|
-
)
|
|
1690
|
-
# Clean up the temp directories.
|
|
1691
|
-
shutil.rmtree(temp_dir)
|
|
1692
|
-
shutil.rmtree(unzip_dir)
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
def initialize_default_app(app_name: str):
|
|
1696
|
-
"""Initialize the default app.
|
|
1697
|
-
|
|
1698
|
-
Args:
|
|
1699
|
-
app_name: The name of the app.
|
|
1700
|
-
"""
|
|
1701
|
-
create_config(app_name)
|
|
1702
|
-
initialize_app_directory(app_name)
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
def validate_and_create_app_using_remote_template(
|
|
1706
|
-
app_name: str, template: str, templates: dict[str, Template]
|
|
1707
|
-
):
|
|
1708
|
-
"""Validate and create an app using a remote template.
|
|
1709
|
-
|
|
1710
|
-
Args:
|
|
1711
|
-
app_name: The name of the app.
|
|
1712
|
-
template: The name of the template.
|
|
1713
|
-
templates: The available templates.
|
|
1714
|
-
|
|
1715
|
-
Raises:
|
|
1716
|
-
Exit: If the template is not found.
|
|
1717
|
-
"""
|
|
1718
|
-
# If user selects a template, it needs to exist.
|
|
1719
|
-
if template in templates:
|
|
1720
|
-
from reflex_cli.v2.utils import hosting
|
|
1721
|
-
|
|
1722
|
-
authenticated_token = hosting.authenticated_token()
|
|
1723
|
-
if not authenticated_token or not authenticated_token[0]:
|
|
1724
|
-
console.print(
|
|
1725
|
-
f"Please use `reflex login` to access the '{template}' template."
|
|
1726
|
-
)
|
|
1727
|
-
raise click.exceptions.Exit(3)
|
|
1728
|
-
|
|
1729
|
-
template_url = templates[template].code_url
|
|
1730
|
-
else:
|
|
1731
|
-
template_parsed_url = urlparse(template)
|
|
1732
|
-
# Check if the template is a github repo.
|
|
1733
|
-
if template_parsed_url.hostname == "github.com":
|
|
1734
|
-
path = template_parsed_url.path.strip("/").removesuffix(".git")
|
|
1735
|
-
template_url = f"https://github.com/{path}/archive/main.zip"
|
|
1736
|
-
else:
|
|
1737
|
-
console.error(f"Template `{template}` not found or invalid.")
|
|
1738
|
-
raise click.exceptions.Exit(1)
|
|
1739
|
-
|
|
1740
|
-
if template_url is None:
|
|
1741
|
-
return
|
|
1742
|
-
|
|
1743
|
-
create_config_init_app_from_remote_template(
|
|
1744
|
-
app_name=app_name, template_url=template_url
|
|
1745
|
-
)
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
def fetch_remote_templates(
|
|
1749
|
-
template: str,
|
|
1750
|
-
) -> tuple[str, dict[str, Template]]:
|
|
1751
|
-
"""Fetch the available remote templates.
|
|
1752
|
-
|
|
1753
|
-
Args:
|
|
1754
|
-
template: The name of the template.
|
|
1755
|
-
|
|
1756
|
-
Returns:
|
|
1757
|
-
The selected template and the available templates.
|
|
1758
|
-
"""
|
|
1759
|
-
available_templates = {}
|
|
1760
|
-
|
|
1761
|
-
try:
|
|
1762
|
-
# Get the available templates
|
|
1763
|
-
available_templates = fetch_app_templates(constants.Reflex.VERSION)
|
|
1764
|
-
except Exception as e:
|
|
1765
|
-
console.warn("Failed to fetch templates. Falling back to default template.")
|
|
1766
|
-
console.debug(f"Error while fetching templates: {e}")
|
|
1767
|
-
template = constants.Templates.DEFAULT
|
|
1768
|
-
|
|
1769
|
-
return template, available_templates
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
def initialize_app(app_name: str, template: str | None = None) -> str | None:
|
|
1773
|
-
"""Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit.
|
|
1774
|
-
|
|
1775
|
-
Args:
|
|
1776
|
-
app_name: The name of the app.
|
|
1777
|
-
template: The name of the template to use.
|
|
1778
|
-
|
|
1779
|
-
Returns:
|
|
1780
|
-
The name of the template.
|
|
1781
|
-
|
|
1782
|
-
Raises:
|
|
1783
|
-
Exit: If the template is not valid or unspecified.
|
|
1784
|
-
"""
|
|
1785
|
-
# Local imports to avoid circular imports.
|
|
1786
|
-
from reflex.utils import telemetry
|
|
1787
|
-
|
|
1788
|
-
# Check if the app is already initialized.
|
|
1789
|
-
if constants.Config.FILE.exists():
|
|
1790
|
-
telemetry.send("reinit")
|
|
1791
|
-
return None
|
|
1792
|
-
|
|
1793
|
-
templates: dict[str, Template] = {}
|
|
1794
|
-
|
|
1795
|
-
# Don't fetch app templates if the user directly asked for DEFAULT.
|
|
1796
|
-
if template is not None and (template not in (constants.Templates.DEFAULT,)):
|
|
1797
|
-
template, templates = fetch_remote_templates(template)
|
|
1798
|
-
|
|
1799
|
-
if template is None:
|
|
1800
|
-
template = prompt_for_template_options(get_init_cli_prompt_options())
|
|
1801
|
-
|
|
1802
|
-
if template == constants.Templates.CHOOSE_TEMPLATES:
|
|
1803
|
-
redir.reflex_templates()
|
|
1804
|
-
raise click.exceptions.Exit(0)
|
|
1805
|
-
|
|
1806
|
-
if template == constants.Templates.AI:
|
|
1807
|
-
redir.reflex_build_redirect()
|
|
1808
|
-
raise click.exceptions.Exit(0)
|
|
1809
|
-
|
|
1810
|
-
# If the blank template is selected, create a blank app.
|
|
1811
|
-
if template == constants.Templates.DEFAULT:
|
|
1812
|
-
# Default app creation behavior: a blank app.
|
|
1813
|
-
initialize_default_app(app_name)
|
|
1814
|
-
else:
|
|
1815
|
-
validate_and_create_app_using_remote_template(
|
|
1816
|
-
app_name=app_name, template=template, templates=templates
|
|
1817
|
-
)
|
|
1818
|
-
|
|
1819
|
-
telemetry.send("init", template=template)
|
|
1820
|
-
|
|
1821
|
-
return template
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
def get_init_cli_prompt_options() -> list[Template]:
|
|
1825
|
-
"""Get the CLI options for initializing a Reflex app.
|
|
1826
|
-
|
|
1827
|
-
Returns:
|
|
1828
|
-
The CLI options.
|
|
1829
|
-
"""
|
|
1830
|
-
return [
|
|
1831
|
-
Template(
|
|
1832
|
-
name=constants.Templates.DEFAULT,
|
|
1833
|
-
description="A blank Reflex app.",
|
|
1834
|
-
code_url="",
|
|
1835
|
-
),
|
|
1836
|
-
Template(
|
|
1837
|
-
name=constants.Templates.AI,
|
|
1838
|
-
description="[bold]Try our free AI builder.",
|
|
1839
|
-
code_url="",
|
|
1840
|
-
),
|
|
1841
|
-
Template(
|
|
1842
|
-
name=constants.Templates.CHOOSE_TEMPLATES,
|
|
1843
|
-
description="Premade templates built by the Reflex team.",
|
|
1844
|
-
code_url="",
|
|
1845
|
-
),
|
|
1846
|
-
]
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
def format_address_width(address_width: str | None) -> int | None:
|
|
1850
|
-
"""Cast address width to an int.
|
|
1851
|
-
|
|
1852
|
-
Args:
|
|
1853
|
-
address_width: The address width.
|
|
1854
|
-
|
|
1855
|
-
Returns:
|
|
1856
|
-
Address width int
|
|
1857
|
-
"""
|
|
1858
|
-
try:
|
|
1859
|
-
return int(address_width) if address_width else None
|
|
1860
|
-
except ValueError:
|
|
1861
|
-
return None
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
def _retrieve_cpu_info() -> CpuInfo | None:
|
|
1865
|
-
"""Retrieve the CPU info of the host.
|
|
1866
|
-
|
|
1867
|
-
Returns:
|
|
1868
|
-
The CPU info.
|
|
1869
|
-
"""
|
|
1870
|
-
platform_os = platform.system()
|
|
1871
|
-
cpuinfo = {}
|
|
1872
|
-
try:
|
|
1873
|
-
if platform_os == "Windows":
|
|
1874
|
-
cmd = 'powershell -Command "Get-CimInstance Win32_Processor | Select-Object -First 1 | Select-Object AddressWidth,Manufacturer,Name | ConvertTo-Json"'
|
|
1875
|
-
output = processes.execute_command_and_return_output(cmd)
|
|
1876
|
-
if output:
|
|
1877
|
-
cpu_data = json.loads(output)
|
|
1878
|
-
cpuinfo["address_width"] = cpu_data["AddressWidth"]
|
|
1879
|
-
cpuinfo["manufacturer_id"] = cpu_data["Manufacturer"]
|
|
1880
|
-
cpuinfo["model_name"] = cpu_data["Name"]
|
|
1881
|
-
elif platform_os == "Linux":
|
|
1882
|
-
output = processes.execute_command_and_return_output("lscpu")
|
|
1883
|
-
if output:
|
|
1884
|
-
lines = output.split("\n")
|
|
1885
|
-
for line in lines:
|
|
1886
|
-
if "Architecture" in line:
|
|
1887
|
-
cpuinfo["address_width"] = (
|
|
1888
|
-
64 if line.split(":")[1].strip() == "x86_64" else 32
|
|
1889
|
-
)
|
|
1890
|
-
if "Vendor ID:" in line:
|
|
1891
|
-
cpuinfo["manufacturer_id"] = line.split(":")[1].strip()
|
|
1892
|
-
if "Model name" in line:
|
|
1893
|
-
cpuinfo["model_name"] = line.split(":")[1].strip()
|
|
1894
|
-
elif platform_os == "Darwin":
|
|
1895
|
-
cpuinfo["address_width"] = format_address_width(
|
|
1896
|
-
processes.execute_command_and_return_output("getconf LONG_BIT")
|
|
1897
|
-
)
|
|
1898
|
-
cpuinfo["manufacturer_id"] = processes.execute_command_and_return_output(
|
|
1899
|
-
"sysctl -n machdep.cpu.brand_string"
|
|
1900
|
-
)
|
|
1901
|
-
cpuinfo["model_name"] = processes.execute_command_and_return_output(
|
|
1902
|
-
"uname -m"
|
|
1903
|
-
)
|
|
1904
|
-
except Exception as err:
|
|
1905
|
-
console.error(f"Failed to retrieve CPU info. {err}")
|
|
1906
|
-
return None
|
|
1907
|
-
|
|
1908
|
-
return (
|
|
1909
|
-
CpuInfo(
|
|
1910
|
-
manufacturer_id=cpuinfo.get("manufacturer_id"),
|
|
1911
|
-
model_name=cpuinfo.get("model_name"),
|
|
1912
|
-
address_width=cpuinfo.get("address_width"),
|
|
1913
|
-
)
|
|
1914
|
-
if cpuinfo
|
|
1915
|
-
else None
|
|
1916
|
-
)
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
@functools.cache
|
|
1920
|
-
def get_cpu_info() -> CpuInfo | None:
|
|
1921
|
-
"""Get the CPU info of the underlining host.
|
|
1922
|
-
|
|
1923
|
-
Returns:
|
|
1924
|
-
The CPU info.
|
|
1925
|
-
"""
|
|
1926
|
-
cpu_info_file = environment.REFLEX_DIR.get() / "cpu_info.json"
|
|
1927
|
-
if cpu_info_file.exists() and (cpu_info := json.loads(cpu_info_file.read_text())):
|
|
1928
|
-
return CpuInfo(**cpu_info)
|
|
1929
|
-
cpu_info = _retrieve_cpu_info()
|
|
1930
|
-
if cpu_info:
|
|
1931
|
-
cpu_info_file.parent.mkdir(parents=True, exist_ok=True)
|
|
1932
|
-
cpu_info_file.write_text(json.dumps(dataclasses.asdict(cpu_info)))
|
|
1933
|
-
return cpu_info
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
def is_generation_hash(template: str) -> bool:
|
|
1937
|
-
"""Check if the template looks like a generation hash.
|
|
1938
|
-
|
|
1939
|
-
Args:
|
|
1940
|
-
template: The template name.
|
|
1941
|
-
|
|
1942
|
-
Returns:
|
|
1943
|
-
True if the template is composed of 32 or more hex characters.
|
|
1944
|
-
"""
|
|
1945
|
-
return re.match(r"^[0-9a-f]{32,}$", template) is not None
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
582
|
def get_user_tier():
|
|
1949
583
|
"""Get the current user's tier.
|
|
1950
584
|
|