reflex 0.8.6a1__py3-none-any.whl → 0.8.7__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 +15 -23
- 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/reflex.py +7 -6
- reflex/route.py +4 -0
- reflex/state.py +1 -1
- 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.6a1.dist-info → reflex-0.8.7.dist-info}/METADATA +2 -2
- {reflex-0.8.6a1.dist-info → reflex-0.8.7.dist-info}/RECORD +34 -33
- reflex/.templates/web/utils/client_side_routing.js +0 -45
- reflex/components/core/client_side_routing.py +0 -70
- reflex/components/core/client_side_routing.pyi +0 -68
- {reflex-0.8.6a1.dist-info → reflex-0.8.7.dist-info}/WHEEL +0 -0
- {reflex-0.8.6a1.dist-info → reflex-0.8.7.dist-info}/entry_points.txt +0 -0
- {reflex-0.8.6a1.dist-info → reflex-0.8.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"""This module provides utilities for managing JavaScript runtimes like Node.js and Bun."""
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from packaging import version
|
|
11
|
+
|
|
12
|
+
from reflex import constants
|
|
13
|
+
from reflex.config import Config, get_config
|
|
14
|
+
from reflex.environment import environment
|
|
15
|
+
from reflex.utils import console, net, path_ops, processes
|
|
16
|
+
from reflex.utils.decorator import cached_procedure, once
|
|
17
|
+
from reflex.utils.exceptions import SystemPackageMissingError
|
|
18
|
+
from reflex.utils.prerequisites import get_web_dir, windows_check_onedrive_in_path
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def check_node_version() -> bool:
|
|
22
|
+
"""Check the version of Node.js.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Whether the version of Node.js is valid.
|
|
26
|
+
"""
|
|
27
|
+
current_version = get_node_version()
|
|
28
|
+
return current_version is not None and current_version >= version.parse(
|
|
29
|
+
constants.Node.MIN_VERSION
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@once
|
|
34
|
+
def get_node_version() -> version.Version | None:
|
|
35
|
+
"""Get the version of node.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
The version of node.
|
|
39
|
+
"""
|
|
40
|
+
node_path = path_ops.get_node_path()
|
|
41
|
+
if node_path is None:
|
|
42
|
+
return None
|
|
43
|
+
try:
|
|
44
|
+
result = processes.new_process([node_path, "-v"], run=True)
|
|
45
|
+
# The output will be in the form "vX.Y.Z", but version.parse() can handle it
|
|
46
|
+
return version.parse(result.stdout)
|
|
47
|
+
except (FileNotFoundError, TypeError):
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_bun_version(bun_path: Path | None = None) -> version.Version | None:
|
|
52
|
+
"""Get the version of bun.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
bun_path: The path to the bun executable.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
The version of bun.
|
|
59
|
+
"""
|
|
60
|
+
bun_path = bun_path or path_ops.get_bun_path()
|
|
61
|
+
if bun_path is None:
|
|
62
|
+
return None
|
|
63
|
+
try:
|
|
64
|
+
# Run the bun -v command and capture the output
|
|
65
|
+
result = processes.new_process([str(bun_path), "-v"], run=True)
|
|
66
|
+
return version.parse(str(result.stdout))
|
|
67
|
+
except FileNotFoundError:
|
|
68
|
+
return None
|
|
69
|
+
except version.InvalidVersion as e:
|
|
70
|
+
console.warn(
|
|
71
|
+
f"The detected bun version ({e.args[0]}) is not valid. Defaulting to None."
|
|
72
|
+
)
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def npm_escape_hatch() -> bool:
|
|
77
|
+
"""If the user sets REFLEX_USE_NPM, prefer npm over bun.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
If the user has set REFLEX_USE_NPM.
|
|
81
|
+
"""
|
|
82
|
+
return environment.REFLEX_USE_NPM.get()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def prefer_npm_over_bun() -> bool:
|
|
86
|
+
"""Check if npm should be preferred over bun.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
If npm should be preferred over bun.
|
|
90
|
+
"""
|
|
91
|
+
return npm_escape_hatch() or (
|
|
92
|
+
constants.IS_WINDOWS and windows_check_onedrive_in_path()
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_nodejs_compatible_package_managers(
|
|
97
|
+
raise_on_none: bool = True,
|
|
98
|
+
) -> Sequence[str]:
|
|
99
|
+
"""Get the package manager executable for installation. Typically, bun is used for installation.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
raise_on_none: Whether to raise an error if the package manager is not found.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The path to the package manager.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
FileNotFoundError: If the package manager is not found and raise_on_none is True.
|
|
109
|
+
"""
|
|
110
|
+
bun_package_manager = (
|
|
111
|
+
str(bun_path) if (bun_path := path_ops.get_bun_path()) else None
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
npm_package_manager = (
|
|
115
|
+
str(npm_path) if (npm_path := path_ops.get_npm_path()) else None
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if prefer_npm_over_bun():
|
|
119
|
+
package_managers = [npm_package_manager, bun_package_manager]
|
|
120
|
+
else:
|
|
121
|
+
package_managers = [bun_package_manager, npm_package_manager]
|
|
122
|
+
|
|
123
|
+
package_managers = list(filter(None, package_managers))
|
|
124
|
+
|
|
125
|
+
if not package_managers and raise_on_none:
|
|
126
|
+
msg = "Bun or npm not found. You might need to rerun `reflex init` or install either."
|
|
127
|
+
raise FileNotFoundError(msg)
|
|
128
|
+
|
|
129
|
+
return package_managers
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def is_outdated_nodejs_installed():
|
|
133
|
+
"""Check if the installed Node.js version is outdated.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
If the installed Node.js version is outdated.
|
|
137
|
+
"""
|
|
138
|
+
current_version = get_node_version()
|
|
139
|
+
if current_version is not None and current_version < version.parse(
|
|
140
|
+
constants.Node.MIN_VERSION
|
|
141
|
+
):
|
|
142
|
+
console.warn(
|
|
143
|
+
f"Your version ({current_version}) of Node.js is out of date. Upgrade to {constants.Node.MIN_VERSION} or higher."
|
|
144
|
+
)
|
|
145
|
+
return True
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def get_js_package_executor(raise_on_none: bool = False) -> Sequence[Sequence[str]]:
|
|
150
|
+
"""Get the paths to package managers for running commands. Ordered by preference.
|
|
151
|
+
This is currently identical to get_install_package_managers, but may change in the future.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
raise_on_none: Whether to raise an error if no package managers is not found.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
The paths to the package managers as a list of lists, where each list is the command to run and its arguments.
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
FileNotFoundError: If no package managers are found and raise_on_none is True.
|
|
161
|
+
"""
|
|
162
|
+
bun_package_manager = (
|
|
163
|
+
[str(bun_path)] + (["--bun"] if is_outdated_nodejs_installed() else [])
|
|
164
|
+
if (bun_path := path_ops.get_bun_path())
|
|
165
|
+
else None
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
npm_package_manager = (
|
|
169
|
+
[str(npm_path)] if (npm_path := path_ops.get_npm_path()) else None
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if prefer_npm_over_bun():
|
|
173
|
+
package_managers = [npm_package_manager, bun_package_manager]
|
|
174
|
+
else:
|
|
175
|
+
package_managers = [bun_package_manager, npm_package_manager]
|
|
176
|
+
|
|
177
|
+
package_managers = list(filter(None, package_managers))
|
|
178
|
+
|
|
179
|
+
if not package_managers and raise_on_none:
|
|
180
|
+
msg = "Bun or npm not found. You might need to rerun `reflex init` or install either."
|
|
181
|
+
raise FileNotFoundError(msg)
|
|
182
|
+
|
|
183
|
+
return package_managers
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def download_and_run(url: str, *args, show_status: bool = False, **env):
|
|
187
|
+
"""Download and run a script.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
url: The url of the script.
|
|
191
|
+
args: The arguments to pass to the script.
|
|
192
|
+
show_status: Whether to show the status of the script.
|
|
193
|
+
env: The environment variables to use.
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
Exit: If the script fails to download.
|
|
197
|
+
"""
|
|
198
|
+
import httpx
|
|
199
|
+
|
|
200
|
+
# Download the script
|
|
201
|
+
console.debug(f"Downloading {url}")
|
|
202
|
+
try:
|
|
203
|
+
response = net.get(url)
|
|
204
|
+
response.raise_for_status()
|
|
205
|
+
except httpx.HTTPError as e:
|
|
206
|
+
console.error(
|
|
207
|
+
f"Failed to download bun install script. You can install or update bun manually from https://bun.com \n{e}"
|
|
208
|
+
)
|
|
209
|
+
raise click.exceptions.Exit(1) from None
|
|
210
|
+
|
|
211
|
+
# Save the script to a temporary file.
|
|
212
|
+
with tempfile.NamedTemporaryFile() as tempfile_file:
|
|
213
|
+
script = Path(tempfile_file.name)
|
|
214
|
+
|
|
215
|
+
script.write_text(response.text)
|
|
216
|
+
|
|
217
|
+
# Run the script.
|
|
218
|
+
env = {**os.environ, **env}
|
|
219
|
+
process = processes.new_process(["bash", str(script), *args], env=env)
|
|
220
|
+
show = processes.show_status if show_status else processes.show_logs
|
|
221
|
+
show(f"Installing {url}", process)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def install_bun():
|
|
225
|
+
"""Install bun onto the user's system.
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
SystemPackageMissingError: If "unzip" is missing.
|
|
229
|
+
Exit: If REFLEX_USE_NPM is set but Node.js is not installed.
|
|
230
|
+
"""
|
|
231
|
+
if npm_escape_hatch():
|
|
232
|
+
if get_node_version() is not None:
|
|
233
|
+
console.info(
|
|
234
|
+
"Skipping bun installation as REFLEX_USE_NPM is set. Using npm instead."
|
|
235
|
+
)
|
|
236
|
+
return
|
|
237
|
+
console.error(
|
|
238
|
+
"REFLEX_USE_NPM is set, but Node.js is not installed. Please install Node.js to use npm."
|
|
239
|
+
)
|
|
240
|
+
raise click.exceptions.Exit(1)
|
|
241
|
+
|
|
242
|
+
bun_path = path_ops.get_bun_path()
|
|
243
|
+
|
|
244
|
+
# Skip if bun is already installed.
|
|
245
|
+
if (
|
|
246
|
+
bun_path
|
|
247
|
+
and (current_version := get_bun_version(bun_path=bun_path))
|
|
248
|
+
and current_version >= version.parse(constants.Bun.MIN_VERSION)
|
|
249
|
+
):
|
|
250
|
+
console.debug("Skipping bun installation as it is already installed.")
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
if bun_path and path_ops.use_system_bun():
|
|
254
|
+
validate_bun(bun_path=bun_path)
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
if constants.IS_WINDOWS:
|
|
258
|
+
processes.new_process(
|
|
259
|
+
[
|
|
260
|
+
"powershell",
|
|
261
|
+
"-c",
|
|
262
|
+
f"irm {constants.Bun.WINDOWS_INSTALL_URL}|iex",
|
|
263
|
+
],
|
|
264
|
+
env={
|
|
265
|
+
"BUN_INSTALL": str(constants.Bun.ROOT_PATH),
|
|
266
|
+
"BUN_VERSION": constants.Bun.VERSION,
|
|
267
|
+
},
|
|
268
|
+
shell=True,
|
|
269
|
+
run=True,
|
|
270
|
+
show_logs=console.is_debug(),
|
|
271
|
+
)
|
|
272
|
+
else:
|
|
273
|
+
if path_ops.which("unzip") is None:
|
|
274
|
+
msg = "unzip"
|
|
275
|
+
raise SystemPackageMissingError(msg)
|
|
276
|
+
|
|
277
|
+
# Run the bun install script.
|
|
278
|
+
download_and_run(
|
|
279
|
+
constants.Bun.INSTALL_URL,
|
|
280
|
+
f"bun-v{constants.Bun.VERSION}",
|
|
281
|
+
BUN_INSTALL=str(constants.Bun.ROOT_PATH),
|
|
282
|
+
BUN_VERSION=str(constants.Bun.VERSION),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def validate_bun(bun_path: Path | None = None):
|
|
287
|
+
"""Validate bun if a custom bun path is specified to ensure the bun version meets requirements.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
bun_path: The path to the bun executable. If None, the default bun path is used.
|
|
291
|
+
|
|
292
|
+
Raises:
|
|
293
|
+
Exit: If custom specified bun does not exist or does not meet requirements.
|
|
294
|
+
"""
|
|
295
|
+
bun_path = bun_path or path_ops.get_bun_path()
|
|
296
|
+
|
|
297
|
+
if bun_path is None:
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
if not path_ops.samefile(bun_path, constants.Bun.DEFAULT_PATH):
|
|
301
|
+
console.info(f"Using custom Bun path: {bun_path}")
|
|
302
|
+
bun_version = get_bun_version(bun_path=bun_path)
|
|
303
|
+
if bun_version is None:
|
|
304
|
+
console.error(
|
|
305
|
+
"Failed to obtain bun version. Make sure the specified bun path in your config is correct."
|
|
306
|
+
)
|
|
307
|
+
raise click.exceptions.Exit(1)
|
|
308
|
+
if bun_version < version.parse(constants.Bun.MIN_VERSION):
|
|
309
|
+
console.warn(
|
|
310
|
+
f"Reflex requires bun version {constants.Bun.MIN_VERSION} or higher to run, but the detected version is "
|
|
311
|
+
f"{bun_version}. If you have specified a custom bun path in your config, make sure to provide one "
|
|
312
|
+
f"that satisfies the minimum version requirement. You can upgrade bun by running [bold]bun upgrade[/bold]."
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def validate_frontend_dependencies(init: bool = True):
|
|
317
|
+
"""Validate frontend dependencies to ensure they meet requirements.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
init: whether running `reflex init`
|
|
321
|
+
|
|
322
|
+
Raises:
|
|
323
|
+
Exit: If the package manager is invalid.
|
|
324
|
+
"""
|
|
325
|
+
if not init:
|
|
326
|
+
try:
|
|
327
|
+
get_js_package_executor(raise_on_none=True)
|
|
328
|
+
except FileNotFoundError as e:
|
|
329
|
+
raise click.exceptions.Exit(1) from e
|
|
330
|
+
|
|
331
|
+
if prefer_npm_over_bun() and not check_node_version():
|
|
332
|
+
node_version = get_node_version()
|
|
333
|
+
console.error(
|
|
334
|
+
f"Reflex requires node version {constants.Node.MIN_VERSION} or higher to run, but the detected version is {node_version}",
|
|
335
|
+
)
|
|
336
|
+
raise click.exceptions.Exit(1)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def remove_existing_bun_installation():
|
|
340
|
+
"""Remove existing bun installation."""
|
|
341
|
+
console.debug("Removing existing bun installation.")
|
|
342
|
+
if Path(get_config().bun_path).exists():
|
|
343
|
+
path_ops.rm(constants.Bun.ROOT_PATH)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@cached_procedure(
|
|
347
|
+
cache_file_path=lambda: get_web_dir() / "reflex.install_frontend_packages.cached",
|
|
348
|
+
payload_fn=lambda packages, config: f"{sorted(packages)!r},{config.json()}",
|
|
349
|
+
)
|
|
350
|
+
def install_frontend_packages(packages: set[str], config: Config):
|
|
351
|
+
"""Installs the base and custom frontend packages.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
packages: A list of package names to be installed.
|
|
355
|
+
config: The config object.
|
|
356
|
+
|
|
357
|
+
Example:
|
|
358
|
+
>>> install_frontend_packages(["react", "react-dom"], get_config())
|
|
359
|
+
"""
|
|
360
|
+
install_package_managers = get_nodejs_compatible_package_managers(
|
|
361
|
+
raise_on_none=True
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
env = (
|
|
365
|
+
{
|
|
366
|
+
"NODE_TLS_REJECT_UNAUTHORIZED": "0",
|
|
367
|
+
}
|
|
368
|
+
if environment.SSL_NO_VERIFY.get()
|
|
369
|
+
else {}
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
primary_package_manager = install_package_managers[0]
|
|
373
|
+
fallbacks = install_package_managers[1:]
|
|
374
|
+
|
|
375
|
+
run_package_manager = functools.partial(
|
|
376
|
+
processes.run_process_with_fallbacks,
|
|
377
|
+
fallbacks=fallbacks,
|
|
378
|
+
analytics_enabled=True,
|
|
379
|
+
cwd=get_web_dir(),
|
|
380
|
+
shell=constants.IS_WINDOWS,
|
|
381
|
+
env=env,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
run_package_manager(
|
|
385
|
+
[primary_package_manager, "install", "--legacy-peer-deps"],
|
|
386
|
+
show_status_message="Installing base frontend packages",
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
development_deps: set[str] = set()
|
|
390
|
+
for plugin in config.plugins:
|
|
391
|
+
development_deps.update(plugin.get_frontend_development_dependencies())
|
|
392
|
+
packages.update(plugin.get_frontend_dependencies())
|
|
393
|
+
|
|
394
|
+
if development_deps:
|
|
395
|
+
run_package_manager(
|
|
396
|
+
[
|
|
397
|
+
primary_package_manager,
|
|
398
|
+
"add",
|
|
399
|
+
"--legacy-peer-deps",
|
|
400
|
+
"-d",
|
|
401
|
+
*development_deps,
|
|
402
|
+
],
|
|
403
|
+
show_status_message="Installing frontend development dependencies",
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# Install custom packages defined in frontend_packages
|
|
407
|
+
if packages:
|
|
408
|
+
run_package_manager(
|
|
409
|
+
[primary_package_manager, "add", "--legacy-peer-deps", *packages],
|
|
410
|
+
show_status_message="Installing frontend packages from config and components",
|
|
411
|
+
)
|