reflex 0.8.6a1__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.

Files changed (36) hide show
  1. reflex/.templates/jinja/web/vite.config.js.jinja2 +4 -1
  2. reflex/.templates/web/app/routes.js +0 -1
  3. reflex/.templates/web/utils/state.js +1 -11
  4. reflex/app.py +15 -23
  5. reflex/components/component.py +6 -4
  6. reflex/components/lucide/icon.py +4 -1
  7. reflex/components/lucide/icon.pyi +4 -1
  8. reflex/components/plotly/plotly.py +9 -9
  9. reflex/components/recharts/recharts.py +2 -2
  10. reflex/components/sonner/toast.py +7 -7
  11. reflex/components/sonner/toast.pyi +8 -8
  12. reflex/config.py +9 -2
  13. reflex/constants/base.py +2 -0
  14. reflex/constants/installer.py +6 -6
  15. reflex/constants/state.py +1 -0
  16. reflex/custom_components/custom_components.py +3 -3
  17. reflex/reflex.py +7 -6
  18. reflex/route.py +4 -0
  19. reflex/state.py +1 -1
  20. reflex/testing.py +3 -5
  21. reflex/utils/build.py +21 -3
  22. reflex/utils/exec.py +11 -11
  23. reflex/utils/frontend_skeleton.py +254 -0
  24. reflex/utils/js_runtimes.py +411 -0
  25. reflex/utils/prerequisites.py +17 -1383
  26. reflex/utils/rename.py +170 -0
  27. reflex/utils/telemetry.py +101 -10
  28. reflex/utils/templates.py +443 -0
  29. reflex/vars/base.py +3 -3
  30. {reflex-0.8.6a1.dist-info → reflex-0.8.7a1.dist-info}/METADATA +2 -2
  31. {reflex-0.8.6a1.dist-info → reflex-0.8.7a1.dist-info}/RECORD +34 -32
  32. reflex/.templates/web/utils/client_side_routing.js +0 -45
  33. reflex/components/core/client_side_routing.py +0 -70
  34. {reflex-0.8.6a1.dist-info → reflex-0.8.7a1.dist-info}/WHEEL +0 -0
  35. {reflex-0.8.6a1.dist-info → reflex-0.8.7a1.dist-info}/entry_points.txt +0 -0
  36. {reflex-0.8.6a1.dist-info → reflex-0.8.7a1.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
+ )