reflex 0.7.8a1__py3-none-any.whl → 0.7.9a1__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/tailwind.config.js.jinja2 +65 -31
- reflex/.templates/web/utils/state.js +11 -1
- reflex/app.py +185 -79
- reflex/app_mixins/lifespan.py +2 -2
- reflex/compiler/compiler.py +31 -4
- reflex/components/base/body.pyi +3 -197
- reflex/components/base/link.pyi +4 -392
- reflex/components/base/meta.pyi +28 -608
- reflex/components/component.py +39 -57
- reflex/components/core/upload.py +8 -0
- reflex/components/dynamic.py +9 -1
- reflex/components/el/elements/metadata.pyi +0 -1
- reflex/components/markdown/markdown.py +0 -21
- reflex/components/markdown/markdown.pyi +2 -2
- reflex/components/radix/primitives/accordion.py +1 -1
- reflex/components/radix/primitives/form.py +1 -1
- reflex/components/radix/primitives/progress.py +1 -1
- reflex/components/radix/primitives/slider.py +1 -1
- reflex/components/radix/themes/color_mode.py +1 -1
- reflex/components/radix/themes/color_mode.pyi +1 -1
- reflex/components/radix/themes/layout/list.pyi +2 -391
- reflex/components/recharts/recharts.py +2 -2
- reflex/components/sonner/toast.py +1 -1
- reflex/config.py +4 -7
- reflex/constants/base.py +21 -0
- reflex/constants/installer.py +6 -6
- reflex/custom_components/custom_components.py +67 -64
- reflex/event.py +2 -0
- reflex/reflex.py +276 -265
- reflex/testing.py +30 -24
- reflex/utils/codespaces.py +6 -2
- reflex/utils/console.py +4 -3
- reflex/utils/exec.py +60 -24
- reflex/utils/format.py +17 -2
- reflex/utils/prerequisites.py +43 -30
- reflex/utils/processes.py +6 -6
- reflex/utils/types.py +11 -6
- reflex/vars/base.py +19 -1
- {reflex-0.7.8a1.dist-info → reflex-0.7.9a1.dist-info}/METADATA +6 -9
- {reflex-0.7.8a1.dist-info → reflex-0.7.9a1.dist-info}/RECORD +43 -43
- {reflex-0.7.8a1.dist-info → reflex-0.7.9a1.dist-info}/WHEEL +0 -0
- {reflex-0.7.8a1.dist-info → reflex-0.7.9a1.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.8a1.dist-info → reflex-0.7.9a1.dist-info}/licenses/LICENSE +0 -0
reflex/testing.py
CHANGED
|
@@ -34,7 +34,7 @@ import reflex.utils.format
|
|
|
34
34
|
import reflex.utils.prerequisites
|
|
35
35
|
import reflex.utils.processes
|
|
36
36
|
from reflex.components.component import CustomComponent
|
|
37
|
-
from reflex.config import environment
|
|
37
|
+
from reflex.config import environment, get_config
|
|
38
38
|
from reflex.state import (
|
|
39
39
|
BaseState,
|
|
40
40
|
StateManager,
|
|
@@ -44,6 +44,7 @@ from reflex.state import (
|
|
|
44
44
|
reload_state_module,
|
|
45
45
|
)
|
|
46
46
|
from reflex.utils import console
|
|
47
|
+
from reflex.utils.export import export
|
|
47
48
|
|
|
48
49
|
try:
|
|
49
50
|
from selenium import webdriver
|
|
@@ -116,7 +117,6 @@ class AppHarness:
|
|
|
116
117
|
backend: uvicorn.Server | None = None
|
|
117
118
|
state_manager: StateManager | None = None
|
|
118
119
|
_frontends: list[WebDriver] = dataclasses.field(default_factory=list)
|
|
119
|
-
_decorated_pages: list = dataclasses.field(default_factory=list)
|
|
120
120
|
|
|
121
121
|
@classmethod
|
|
122
122
|
def create(
|
|
@@ -253,11 +253,11 @@ class AppHarness:
|
|
|
253
253
|
self._get_source_from_app_source(self.app_source),
|
|
254
254
|
]
|
|
255
255
|
)
|
|
256
|
+
get_config().loglevel = reflex.constants.LogLevel.INFO
|
|
256
257
|
with chdir(self.app_path):
|
|
257
258
|
reflex.reflex._init(
|
|
258
259
|
name=self.app_name,
|
|
259
260
|
template=reflex.constants.Templates.DEFAULT,
|
|
260
|
-
loglevel=reflex.constants.LogLevel.INFO,
|
|
261
261
|
)
|
|
262
262
|
self.app_module_path.write_text(source_code)
|
|
263
263
|
else:
|
|
@@ -267,8 +267,6 @@ class AppHarness:
|
|
|
267
267
|
with chdir(self.app_path):
|
|
268
268
|
# ensure config and app are reloaded when testing different app
|
|
269
269
|
reflex.config.get_config(reload=True)
|
|
270
|
-
# Save decorated pages before importing the test app module
|
|
271
|
-
before_decorated_pages = reflex.app.DECORATED_PAGES[self.app_name].copy()
|
|
272
270
|
# Ensure the AppHarness test does not skip State assignment due to running via pytest
|
|
273
271
|
os.environ.pop(reflex.constants.PYTEST_CURRENT_TEST, None)
|
|
274
272
|
os.environ[reflex.constants.APP_HARNESS_FLAG] = "true"
|
|
@@ -276,12 +274,6 @@ class AppHarness:
|
|
|
276
274
|
# Do not reload the module for pre-existing apps (only apps generated from source)
|
|
277
275
|
reload=self.app_source is not None
|
|
278
276
|
)
|
|
279
|
-
# Save the pages that were added during testing
|
|
280
|
-
self._decorated_pages = [
|
|
281
|
-
p
|
|
282
|
-
for p in reflex.app.DECORATED_PAGES[self.app_name]
|
|
283
|
-
if p not in before_decorated_pages
|
|
284
|
-
]
|
|
285
277
|
self.app_instance = self.app_module.app
|
|
286
278
|
if self.app_instance and isinstance(
|
|
287
279
|
self.app_instance._state_manager, StateManagerRedis
|
|
@@ -305,25 +297,37 @@ class AppHarness:
|
|
|
305
297
|
|
|
306
298
|
original_shutdown = self.backend.shutdown
|
|
307
299
|
|
|
308
|
-
async def
|
|
300
|
+
async def _shutdown(*args, **kwargs) -> None:
|
|
309
301
|
# ensure redis is closed before event loop
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
):
|
|
302
|
+
if self.app_instance is not None and isinstance(
|
|
303
|
+
self.app_instance.state_manager, StateManagerRedis
|
|
304
|
+
):
|
|
305
|
+
with contextlib.suppress(ValueError):
|
|
314
306
|
await self.app_instance.state_manager.close()
|
|
307
|
+
|
|
308
|
+
# socketio shutdown handler
|
|
309
|
+
if self.app_instance is not None and self.app_instance.sio is not None:
|
|
310
|
+
with contextlib.suppress(TypeError):
|
|
311
|
+
await self.app_instance.sio.shutdown()
|
|
312
|
+
|
|
313
|
+
# sqlalchemy async engine shutdown handler
|
|
314
|
+
try:
|
|
315
|
+
async_engine = reflex.model.get_async_engine(None)
|
|
315
316
|
except ValueError:
|
|
316
317
|
pass
|
|
318
|
+
else:
|
|
319
|
+
await async_engine.dispose()
|
|
320
|
+
|
|
317
321
|
await original_shutdown(*args, **kwargs)
|
|
318
322
|
|
|
319
|
-
return
|
|
323
|
+
return _shutdown
|
|
320
324
|
|
|
321
325
|
def _start_backend(self, port: int = 0):
|
|
322
|
-
if self.app_instance is None or self.app_instance.
|
|
326
|
+
if self.app_instance is None or self.app_instance._api is None:
|
|
323
327
|
raise RuntimeError("App was not initialized.")
|
|
324
328
|
self.backend = uvicorn.Server(
|
|
325
329
|
uvicorn.Config(
|
|
326
|
-
app=self.app_instance.
|
|
330
|
+
app=self.app_instance._api,
|
|
327
331
|
host="127.0.0.1",
|
|
328
332
|
port=port,
|
|
329
333
|
)
|
|
@@ -488,10 +492,6 @@ class AppHarness:
|
|
|
488
492
|
if self.frontend_output_thread is not None:
|
|
489
493
|
self.frontend_output_thread.join()
|
|
490
494
|
|
|
491
|
-
# Cleanup decorated pages added during testing
|
|
492
|
-
for page in self._decorated_pages:
|
|
493
|
-
reflex.app.DECORATED_PAGES[self.app_name].remove(page)
|
|
494
|
-
|
|
495
495
|
def __exit__(self, *excinfo) -> None:
|
|
496
496
|
"""Contextmanager protocol for `stop()`.
|
|
497
497
|
|
|
@@ -934,7 +934,13 @@ class AppHarnessProd(AppHarness):
|
|
|
934
934
|
config.api_url = "http://{}:{}".format(
|
|
935
935
|
*self._poll_for_servers().getsockname(),
|
|
936
936
|
)
|
|
937
|
-
|
|
937
|
+
|
|
938
|
+
get_config().loglevel = reflex.constants.LogLevel.INFO
|
|
939
|
+
|
|
940
|
+
if reflex.utils.prerequisites.needs_reinit(frontend=True):
|
|
941
|
+
reflex.reflex._init(name=get_config().app_name)
|
|
942
|
+
|
|
943
|
+
export(
|
|
938
944
|
zipping=False,
|
|
939
945
|
frontend=True,
|
|
940
946
|
backend=False,
|
reflex/utils/codespaces.py
CHANGED
|
@@ -4,7 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from starlette.requests import Request
|
|
8
|
+
from starlette.responses import HTMLResponse
|
|
8
9
|
|
|
9
10
|
from reflex.components.base.script import Script
|
|
10
11
|
from reflex.components.component import Component
|
|
@@ -74,9 +75,12 @@ def codespaces_auto_redirect() -> list[Component]:
|
|
|
74
75
|
return []
|
|
75
76
|
|
|
76
77
|
|
|
77
|
-
async def auth_codespace() -> HTMLResponse:
|
|
78
|
+
async def auth_codespace(_request: Request) -> HTMLResponse:
|
|
78
79
|
"""Page automatically redirecting back to the app after authenticating a codespace port forward.
|
|
79
80
|
|
|
81
|
+
Args:
|
|
82
|
+
_request: The request object.
|
|
83
|
+
|
|
80
84
|
Returns:
|
|
81
85
|
An HTML response with an embedded script to redirect back to the app.
|
|
82
86
|
"""
|
reflex/utils/console.py
CHANGED
|
@@ -47,7 +47,7 @@ _EMITTED_LOGS = set()
|
|
|
47
47
|
_EMITTED_PRINTS = set()
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def set_log_level(log_level: LogLevel):
|
|
50
|
+
def set_log_level(log_level: LogLevel | None):
|
|
51
51
|
"""Set the log level.
|
|
52
52
|
|
|
53
53
|
Args:
|
|
@@ -56,6 +56,8 @@ def set_log_level(log_level: LogLevel):
|
|
|
56
56
|
Raises:
|
|
57
57
|
TypeError: If the log level is a string.
|
|
58
58
|
"""
|
|
59
|
+
if log_level is None:
|
|
60
|
+
return
|
|
59
61
|
if not isinstance(log_level, LogLevel):
|
|
60
62
|
raise TypeError(
|
|
61
63
|
f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead."
|
|
@@ -193,13 +195,12 @@ def warn(msg: str, dedupe: bool = False, **kwargs):
|
|
|
193
195
|
|
|
194
196
|
def _get_first_non_framework_frame() -> FrameType | None:
|
|
195
197
|
import click
|
|
196
|
-
import typer
|
|
197
198
|
import typing_extensions
|
|
198
199
|
|
|
199
200
|
import reflex as rx
|
|
200
201
|
|
|
201
202
|
# Exclude utility modules that should never be the source of deprecated reflex usage.
|
|
202
|
-
exclude_modules = [click, rx,
|
|
203
|
+
exclude_modules = [click, rx, typing_extensions]
|
|
203
204
|
exclude_roots = [
|
|
204
205
|
p.parent.resolve() if (p := Path(file)).name == "__init__.py" else p.resolve()
|
|
205
206
|
for m in exclude_modules
|
reflex/utils/exec.py
CHANGED
|
@@ -189,11 +189,9 @@ def run_frontend_prod(root: Path, port: str, backend_present: bool = True):
|
|
|
189
189
|
|
|
190
190
|
@once
|
|
191
191
|
def _warn_user_about_uvicorn():
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
"Using Uvicorn for backend as it is installed. This behavior will change in 0.8.0 to use Granian by default."
|
|
196
|
-
)
|
|
192
|
+
console.warn(
|
|
193
|
+
"Using Uvicorn for backend as it is installed. This behavior will change in 0.8.0 to use Granian by default."
|
|
194
|
+
)
|
|
197
195
|
|
|
198
196
|
|
|
199
197
|
def should_use_granian():
|
|
@@ -202,8 +200,8 @@ def should_use_granian():
|
|
|
202
200
|
Returns:
|
|
203
201
|
True if Granian should be used.
|
|
204
202
|
"""
|
|
205
|
-
if environment.REFLEX_USE_GRANIAN.
|
|
206
|
-
return
|
|
203
|
+
if environment.REFLEX_USE_GRANIAN.is_set():
|
|
204
|
+
return environment.REFLEX_USE_GRANIAN.get()
|
|
207
205
|
if (
|
|
208
206
|
importlib.util.find_spec("uvicorn") is None
|
|
209
207
|
or importlib.util.find_spec("gunicorn") is None
|
|
@@ -219,9 +217,51 @@ def get_app_module():
|
|
|
219
217
|
Returns:
|
|
220
218
|
The app module for the backend.
|
|
221
219
|
"""
|
|
222
|
-
|
|
220
|
+
return get_config().module
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def get_app_instance():
|
|
224
|
+
"""Get the app module for the backend.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
The app module for the backend.
|
|
228
|
+
"""
|
|
229
|
+
return f"{get_app_module()}:{constants.CompileVars.APP}"
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def get_app_file() -> Path:
|
|
233
|
+
"""Get the app file for the backend.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
The app file for the backend.
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
ImportError: If the app module is not found.
|
|
240
|
+
"""
|
|
241
|
+
current_working_dir = str(Path.cwd())
|
|
242
|
+
if current_working_dir not in sys.path:
|
|
243
|
+
# Add the current working directory to sys.path
|
|
244
|
+
sys.path.insert(0, current_working_dir)
|
|
245
|
+
module_spec = importlib.util.find_spec(get_app_module())
|
|
246
|
+
if module_spec is None:
|
|
247
|
+
raise ImportError(
|
|
248
|
+
f"Module {get_app_module()} not found. Make sure the module is installed."
|
|
249
|
+
)
|
|
250
|
+
file_name = module_spec.origin
|
|
251
|
+
if file_name is None:
|
|
252
|
+
raise ImportError(
|
|
253
|
+
f"Module {get_app_module()} not found. Make sure the module is installed."
|
|
254
|
+
)
|
|
255
|
+
return Path(file_name).resolve()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def get_app_instance_from_file() -> str:
|
|
259
|
+
"""Get the app module for the backend.
|
|
223
260
|
|
|
224
|
-
|
|
261
|
+
Returns:
|
|
262
|
+
The app module for the backend.
|
|
263
|
+
"""
|
|
264
|
+
return f"{get_app_file()}:{constants.CompileVars.APP}"
|
|
225
265
|
|
|
226
266
|
|
|
227
267
|
def run_backend(
|
|
@@ -323,7 +363,7 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
323
363
|
import uvicorn
|
|
324
364
|
|
|
325
365
|
uvicorn.run(
|
|
326
|
-
app=f"{
|
|
366
|
+
app=f"{get_app_instance()}",
|
|
327
367
|
factory=True,
|
|
328
368
|
host=host,
|
|
329
369
|
port=port,
|
|
@@ -349,7 +389,7 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
349
389
|
from granian.server import MPServer as Granian
|
|
350
390
|
|
|
351
391
|
Granian(
|
|
352
|
-
target=
|
|
392
|
+
target=get_app_instance_from_file(),
|
|
353
393
|
factory=True,
|
|
354
394
|
address=host,
|
|
355
395
|
port=port,
|
|
@@ -367,14 +407,12 @@ def _deprecate_asgi_config(
|
|
|
367
407
|
config_name: str,
|
|
368
408
|
reason: str = "",
|
|
369
409
|
):
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
removal_version="0.8.0",
|
|
377
|
-
)
|
|
410
|
+
console.deprecate(
|
|
411
|
+
f"config.{config_name}",
|
|
412
|
+
reason=reason,
|
|
413
|
+
deprecation_version="0.7.9",
|
|
414
|
+
removal_version="0.8.0",
|
|
415
|
+
)
|
|
378
416
|
|
|
379
417
|
|
|
380
418
|
@once
|
|
@@ -468,7 +506,7 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
|
|
|
468
506
|
|
|
469
507
|
config = get_config()
|
|
470
508
|
|
|
471
|
-
app_module =
|
|
509
|
+
app_module = get_app_instance()
|
|
472
510
|
|
|
473
511
|
command = (
|
|
474
512
|
[
|
|
@@ -568,7 +606,7 @@ def run_granian_backend_prod(host: str, port: int, loglevel: LogLevel):
|
|
|
568
606
|
*("--host", host),
|
|
569
607
|
*("--port", str(port)),
|
|
570
608
|
*("--interface", str(Interfaces.ASGI)),
|
|
571
|
-
*("--factory",
|
|
609
|
+
*("--factory", get_app_instance_from_file()),
|
|
572
610
|
]
|
|
573
611
|
processes.new_process(
|
|
574
612
|
command,
|
|
@@ -613,9 +651,7 @@ def output_system_info():
|
|
|
613
651
|
)
|
|
614
652
|
|
|
615
653
|
if system == "Linux":
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
os_version = distro.name(pretty=True)
|
|
654
|
+
os_version = platform.freedesktop_os_release().get("PRETTY_NAME", "Unknown")
|
|
619
655
|
else:
|
|
620
656
|
os_version = platform.version()
|
|
621
657
|
|
reflex/utils/format.py
CHANGED
|
@@ -643,17 +643,32 @@ def format_ref(ref: str) -> str:
|
|
|
643
643
|
return f"ref_{clean_ref}"
|
|
644
644
|
|
|
645
645
|
|
|
646
|
-
def format_library_name(library_fullname: str):
|
|
646
|
+
def format_library_name(library_fullname: str | dict[str, Any]) -> str:
|
|
647
647
|
"""Format the name of a library.
|
|
648
648
|
|
|
649
649
|
Args:
|
|
650
|
-
library_fullname: The
|
|
650
|
+
library_fullname: The library reference, either as a string or a dictionary with a 'name' key.
|
|
651
651
|
|
|
652
652
|
Returns:
|
|
653
653
|
The name without the @version if it was part of the name
|
|
654
|
+
|
|
655
|
+
Raises:
|
|
656
|
+
KeyError: If library_fullname is a dictionary without a 'name' key.
|
|
657
|
+
TypeError: If library_fullname or its 'name' value is not a string.
|
|
654
658
|
"""
|
|
659
|
+
# If input is a dictionary, extract the 'name' key
|
|
660
|
+
if isinstance(library_fullname, dict):
|
|
661
|
+
if "name" not in library_fullname:
|
|
662
|
+
raise KeyError("Dictionary input must contain a 'name' key")
|
|
663
|
+
library_fullname = library_fullname["name"]
|
|
664
|
+
|
|
665
|
+
# Process the library name as a string
|
|
666
|
+
if not isinstance(library_fullname, str):
|
|
667
|
+
raise TypeError("Library name must be a string")
|
|
668
|
+
|
|
655
669
|
if library_fullname.startswith("https://"):
|
|
656
670
|
return library_fullname
|
|
671
|
+
|
|
657
672
|
lib, at, version = library_fullname.rpartition("@")
|
|
658
673
|
if not lib:
|
|
659
674
|
lib = at + version
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -27,8 +27,8 @@ from types import ModuleType
|
|
|
27
27
|
from typing import NamedTuple
|
|
28
28
|
from urllib.parse import urlparse
|
|
29
29
|
|
|
30
|
+
import click
|
|
30
31
|
import httpx
|
|
31
|
-
import typer
|
|
32
32
|
from alembic.util.exc import CommandError
|
|
33
33
|
from packaging import version
|
|
34
34
|
from redis import Redis as RedisSync
|
|
@@ -371,7 +371,6 @@ def get_app(reload: bool = False) -> ModuleType:
|
|
|
371
371
|
from reflex.utils import telemetry
|
|
372
372
|
|
|
373
373
|
try:
|
|
374
|
-
environment.RELOAD_CONFIG.set(reload)
|
|
375
374
|
config = get_config()
|
|
376
375
|
|
|
377
376
|
_check_app_name(config)
|
|
@@ -384,11 +383,14 @@ def get_app(reload: bool = False) -> ModuleType:
|
|
|
384
383
|
else config.app_module
|
|
385
384
|
)
|
|
386
385
|
if reload:
|
|
386
|
+
from reflex.page import DECORATED_PAGES
|
|
387
387
|
from reflex.state import reload_state_module
|
|
388
388
|
|
|
389
389
|
# Reset rx.State subclasses to avoid conflict when reloading.
|
|
390
390
|
reload_state_module(module=module)
|
|
391
391
|
|
|
392
|
+
DECORATED_PAGES.clear()
|
|
393
|
+
|
|
392
394
|
# Reload the app module.
|
|
393
395
|
importlib.reload(app)
|
|
394
396
|
except Exception as ex:
|
|
@@ -515,7 +517,7 @@ def compile_or_validate_app(compile: bool = False) -> bool:
|
|
|
515
517
|
else:
|
|
516
518
|
validate_app()
|
|
517
519
|
except Exception as e:
|
|
518
|
-
if isinstance(e,
|
|
520
|
+
if isinstance(e, click.exceptions.Exit):
|
|
519
521
|
return False
|
|
520
522
|
|
|
521
523
|
import traceback
|
|
@@ -619,14 +621,14 @@ def validate_app_name(app_name: str | None = None) -> str:
|
|
|
619
621
|
console.error(
|
|
620
622
|
f"The app directory cannot be named [bold]{constants.Reflex.MODULE_NAME}[/bold]."
|
|
621
623
|
)
|
|
622
|
-
raise
|
|
624
|
+
raise click.exceptions.Exit(1)
|
|
623
625
|
|
|
624
626
|
# Make sure the app name is standard for a python package name.
|
|
625
627
|
if not re.match(r"^[a-zA-Z][a-zA-Z0-9_]*$", app_name):
|
|
626
628
|
console.error(
|
|
627
629
|
"The app directory name must start with a letter and can contain letters, numbers, and underscores."
|
|
628
630
|
)
|
|
629
|
-
raise
|
|
631
|
+
raise click.exceptions.Exit(1)
|
|
630
632
|
|
|
631
633
|
return app_name
|
|
632
634
|
|
|
@@ -685,7 +687,7 @@ def rename_app(new_app_name: str, loglevel: constants.LogLevel):
|
|
|
685
687
|
console.error(
|
|
686
688
|
"No rxconfig.py found. Make sure you are in the root directory of your app."
|
|
687
689
|
)
|
|
688
|
-
raise
|
|
690
|
+
raise click.exceptions.Exit(1)
|
|
689
691
|
|
|
690
692
|
sys.path.insert(0, str(Path.cwd()))
|
|
691
693
|
|
|
@@ -693,11 +695,11 @@ def rename_app(new_app_name: str, loglevel: constants.LogLevel):
|
|
|
693
695
|
module_path = importlib.util.find_spec(config.module)
|
|
694
696
|
if module_path is None:
|
|
695
697
|
console.error(f"Could not find module {config.module}.")
|
|
696
|
-
raise
|
|
698
|
+
raise click.exceptions.Exit(1)
|
|
697
699
|
|
|
698
700
|
if not module_path.origin:
|
|
699
701
|
console.error(f"Could not find origin for module {config.module}.")
|
|
700
|
-
raise
|
|
702
|
+
raise click.exceptions.Exit(1)
|
|
701
703
|
console.info(f"Renaming app directory to {new_app_name}.")
|
|
702
704
|
process_directory(
|
|
703
705
|
Path.cwd(),
|
|
@@ -860,7 +862,7 @@ def initialize_requirements_txt() -> bool:
|
|
|
860
862
|
continue
|
|
861
863
|
except Exception as e:
|
|
862
864
|
console.error(f"Failed to read {requirements_file_path}.")
|
|
863
|
-
raise
|
|
865
|
+
raise click.exceptions.Exit(1) from e
|
|
864
866
|
else:
|
|
865
867
|
return False
|
|
866
868
|
|
|
@@ -905,7 +907,7 @@ def initialize_app_directory(
|
|
|
905
907
|
console.error(
|
|
906
908
|
f"Only {template_name=} should be provided, got {template_code_dir_name=}, {template_dir=}."
|
|
907
909
|
)
|
|
908
|
-
raise
|
|
910
|
+
raise click.exceptions.Exit(1)
|
|
909
911
|
template_code_dir_name = constants.Templates.Dirs.CODE
|
|
910
912
|
template_dir = Path(constants.Templates.Dirs.BASE, "apps", template_name)
|
|
911
913
|
else:
|
|
@@ -913,7 +915,7 @@ def initialize_app_directory(
|
|
|
913
915
|
console.error(
|
|
914
916
|
f"For `{template_name}` template, `template_code_dir_name` and `template_dir` should both be provided."
|
|
915
917
|
)
|
|
916
|
-
raise
|
|
918
|
+
raise click.exceptions.Exit(1)
|
|
917
919
|
|
|
918
920
|
console.debug(f"Using {template_name=} {template_dir=} {template_code_dir_name=}.")
|
|
919
921
|
|
|
@@ -1132,12 +1134,20 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
|
|
|
1132
1134
|
args: The arguments to pass to the script.
|
|
1133
1135
|
show_status: Whether to show the status of the script.
|
|
1134
1136
|
env: The environment variables to use.
|
|
1137
|
+
|
|
1138
|
+
Raises:
|
|
1139
|
+
Exit: If the script fails to download.
|
|
1135
1140
|
"""
|
|
1136
1141
|
# Download the script
|
|
1137
1142
|
console.debug(f"Downloading {url}")
|
|
1138
|
-
|
|
1139
|
-
|
|
1143
|
+
try:
|
|
1144
|
+
response = net.get(url)
|
|
1140
1145
|
response.raise_for_status()
|
|
1146
|
+
except httpx.HTTPError as e:
|
|
1147
|
+
console.error(
|
|
1148
|
+
f"Failed to download bun install script. You can install or update bun manually from https://bun.sh \n{e}"
|
|
1149
|
+
)
|
|
1150
|
+
raise click.exceptions.Exit(1) from None
|
|
1141
1151
|
|
|
1142
1152
|
# Save the script to a temporary file.
|
|
1143
1153
|
script = Path(tempfile.NamedTemporaryFile().name)
|
|
@@ -1314,7 +1324,10 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1314
1324
|
"--legacy-peer-deps",
|
|
1315
1325
|
"-d",
|
|
1316
1326
|
constants.Tailwind.VERSION,
|
|
1317
|
-
*
|
|
1327
|
+
*[
|
|
1328
|
+
plugin if isinstance(plugin, str) else plugin.get("name")
|
|
1329
|
+
for plugin in (config.tailwind or {}).get("plugins", [])
|
|
1330
|
+
],
|
|
1318
1331
|
],
|
|
1319
1332
|
fallbacks=fallbacks,
|
|
1320
1333
|
analytics_enabled=True,
|
|
@@ -1368,7 +1381,7 @@ def needs_reinit(frontend: bool = True) -> bool:
|
|
|
1368
1381
|
console.error(
|
|
1369
1382
|
f"[cyan]{constants.Config.FILE}[/cyan] not found. Move to the root folder of your project, or run [bold]{constants.Reflex.MODULE_NAME} init[/bold] to start a new project."
|
|
1370
1383
|
)
|
|
1371
|
-
raise
|
|
1384
|
+
raise click.exceptions.Exit(1)
|
|
1372
1385
|
|
|
1373
1386
|
# Don't need to reinit if not running in frontend mode.
|
|
1374
1387
|
if not frontend:
|
|
@@ -1433,7 +1446,7 @@ def validate_bun(bun_path: Path | None = None):
|
|
|
1433
1446
|
console.error(
|
|
1434
1447
|
"Failed to obtain bun version. Make sure the specified bun path in your config is correct."
|
|
1435
1448
|
)
|
|
1436
|
-
raise
|
|
1449
|
+
raise click.exceptions.Exit(1)
|
|
1437
1450
|
elif bun_version < version.parse(constants.Bun.MIN_VERSION):
|
|
1438
1451
|
console.warn(
|
|
1439
1452
|
f"Reflex requires bun version {constants.Bun.MIN_VERSION} or higher to run, but the detected version is "
|
|
@@ -1455,14 +1468,14 @@ def validate_frontend_dependencies(init: bool = True):
|
|
|
1455
1468
|
try:
|
|
1456
1469
|
get_js_package_executor(raise_on_none=True)
|
|
1457
1470
|
except FileNotFoundError as e:
|
|
1458
|
-
raise
|
|
1471
|
+
raise click.exceptions.Exit(1) from e
|
|
1459
1472
|
|
|
1460
1473
|
if prefer_npm_over_bun() and not check_node_version():
|
|
1461
1474
|
node_version = get_node_version()
|
|
1462
1475
|
console.error(
|
|
1463
1476
|
f"Reflex requires node version {constants.Node.MIN_VERSION} or higher to run, but the detected version is {node_version}",
|
|
1464
1477
|
)
|
|
1465
|
-
raise
|
|
1478
|
+
raise click.exceptions.Exit(1)
|
|
1466
1479
|
|
|
1467
1480
|
|
|
1468
1481
|
def ensure_reflex_installation_id() -> int | None:
|
|
@@ -1609,17 +1622,17 @@ def prompt_for_template_options(templates: list[Template]) -> str:
|
|
|
1609
1622
|
|
|
1610
1623
|
if not template:
|
|
1611
1624
|
console.error("No template selected.")
|
|
1612
|
-
raise
|
|
1625
|
+
raise click.exceptions.Exit(1)
|
|
1613
1626
|
|
|
1614
1627
|
try:
|
|
1615
1628
|
template_index = int(template)
|
|
1616
1629
|
except ValueError:
|
|
1617
1630
|
console.error("Invalid template selected.")
|
|
1618
|
-
raise
|
|
1631
|
+
raise click.exceptions.Exit(1) from None
|
|
1619
1632
|
|
|
1620
1633
|
if template_index < 0 or template_index >= len(templates):
|
|
1621
1634
|
console.error("Invalid template selected.")
|
|
1622
|
-
raise
|
|
1635
|
+
raise click.exceptions.Exit(1)
|
|
1623
1636
|
|
|
1624
1637
|
# Return the template.
|
|
1625
1638
|
return templates[template_index].name
|
|
@@ -1699,7 +1712,7 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
|
|
|
1699
1712
|
temp_dir = tempfile.mkdtemp()
|
|
1700
1713
|
except OSError as ose:
|
|
1701
1714
|
console.error(f"Failed to create temp directory for download: {ose}")
|
|
1702
|
-
raise
|
|
1715
|
+
raise click.exceptions.Exit(1) from ose
|
|
1703
1716
|
|
|
1704
1717
|
# Use httpx GET with redirects to download the zip file.
|
|
1705
1718
|
zip_file_path: Path = Path(temp_dir) / "template.zip"
|
|
@@ -1710,20 +1723,20 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
|
|
|
1710
1723
|
response.raise_for_status()
|
|
1711
1724
|
except httpx.HTTPError as he:
|
|
1712
1725
|
console.error(f"Failed to download the template: {he}")
|
|
1713
|
-
raise
|
|
1726
|
+
raise click.exceptions.Exit(1) from he
|
|
1714
1727
|
try:
|
|
1715
1728
|
zip_file_path.write_bytes(response.content)
|
|
1716
1729
|
console.debug(f"Downloaded the zip to {zip_file_path}")
|
|
1717
1730
|
except OSError as ose:
|
|
1718
1731
|
console.error(f"Unable to write the downloaded zip to disk {ose}")
|
|
1719
|
-
raise
|
|
1732
|
+
raise click.exceptions.Exit(1) from ose
|
|
1720
1733
|
|
|
1721
1734
|
# Create a temp directory for the zip extraction.
|
|
1722
1735
|
try:
|
|
1723
1736
|
unzip_dir = Path(tempfile.mkdtemp())
|
|
1724
1737
|
except OSError as ose:
|
|
1725
1738
|
console.error(f"Failed to create temp directory for extracting zip: {ose}")
|
|
1726
|
-
raise
|
|
1739
|
+
raise click.exceptions.Exit(1) from ose
|
|
1727
1740
|
|
|
1728
1741
|
try:
|
|
1729
1742
|
zipfile.ZipFile(zip_file_path).extractall(path=unzip_dir)
|
|
@@ -1731,11 +1744,11 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
|
|
|
1731
1744
|
# repo-name-branch/**/*, so we need to remove the top level directory.
|
|
1732
1745
|
except Exception as uze:
|
|
1733
1746
|
console.error(f"Failed to unzip the template: {uze}")
|
|
1734
|
-
raise
|
|
1747
|
+
raise click.exceptions.Exit(1) from uze
|
|
1735
1748
|
|
|
1736
1749
|
if len(subdirs := list(unzip_dir.iterdir())) != 1:
|
|
1737
1750
|
console.error(f"Expected one directory in the zip, found {subdirs}")
|
|
1738
|
-
raise
|
|
1751
|
+
raise click.exceptions.Exit(1)
|
|
1739
1752
|
|
|
1740
1753
|
template_dir = unzip_dir / subdirs[0]
|
|
1741
1754
|
console.debug(f"Template folder is located at {template_dir}")
|
|
@@ -1797,7 +1810,7 @@ def validate_and_create_app_using_remote_template(
|
|
|
1797
1810
|
console.print(
|
|
1798
1811
|
f"Please use `reflex login` to access the '{template}' template."
|
|
1799
1812
|
)
|
|
1800
|
-
raise
|
|
1813
|
+
raise click.exceptions.Exit(3)
|
|
1801
1814
|
|
|
1802
1815
|
template_url = templates[template].code_url
|
|
1803
1816
|
else:
|
|
@@ -1808,7 +1821,7 @@ def validate_and_create_app_using_remote_template(
|
|
|
1808
1821
|
template_url = f"https://github.com/{path}/archive/main.zip"
|
|
1809
1822
|
else:
|
|
1810
1823
|
console.error(f"Template `{template}` not found or invalid.")
|
|
1811
|
-
raise
|
|
1824
|
+
raise click.exceptions.Exit(1)
|
|
1812
1825
|
|
|
1813
1826
|
if template_url is None:
|
|
1814
1827
|
return
|
|
@@ -1875,7 +1888,7 @@ def initialize_app(app_name: str, template: str | None = None) -> str | None:
|
|
|
1875
1888
|
console.print(
|
|
1876
1889
|
f"Go to the templates page ({constants.Templates.REFLEX_TEMPLATES_URL}) and copy the command to init with a template."
|
|
1877
1890
|
)
|
|
1878
|
-
raise
|
|
1891
|
+
raise click.exceptions.Exit(0)
|
|
1879
1892
|
|
|
1880
1893
|
# If the blank template is selected, create a blank app.
|
|
1881
1894
|
if template in (constants.Templates.DEFAULT,):
|