reflex 0.7.4a3__py3-none-any.whl → 0.7.5__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/__init__.py +1 -0
- reflex/__init__.pyi +1 -0
- reflex/app.py +10 -6
- reflex/app_mixins/middleware.py +13 -20
- reflex/compiler/compiler.py +10 -3
- reflex/compiler/utils.py +4 -4
- reflex/components/base/app_wrap.pyi +7 -3
- reflex/components/base/body.pyi +7 -3
- reflex/components/base/document.pyi +27 -7
- reflex/components/base/error_boundary.pyi +7 -3
- reflex/components/base/fragment.pyi +7 -3
- reflex/components/base/head.pyi +12 -4
- reflex/components/base/link.pyi +12 -4
- reflex/components/base/meta.pyi +22 -6
- reflex/components/base/script.pyi +7 -3
- reflex/components/base/strict_mode.pyi +7 -3
- reflex/components/component.py +64 -23
- reflex/components/core/auto_scroll.pyi +7 -3
- reflex/components/core/banner.py +6 -2
- reflex/components/core/banner.pyi +32 -8
- reflex/components/core/client_side_routing.pyi +12 -4
- reflex/components/core/clipboard.pyi +7 -3
- reflex/components/core/debounce.pyi +7 -3
- reflex/components/core/foreach.py +5 -1
- reflex/components/core/html.pyi +7 -3
- reflex/components/core/match.py +5 -5
- reflex/components/core/sticky.pyi +21 -6
- reflex/components/core/upload.pyi +27 -7
- reflex/components/datadisplay/code.pyi +12 -4
- reflex/components/datadisplay/dataeditor.py +2 -2
- reflex/components/datadisplay/dataeditor.pyi +17 -3
- reflex/components/datadisplay/shiki_code_block.pyi +17 -4
- reflex/components/el/__init__.pyi +1 -1
- reflex/components/el/element.pyi +7 -3
- reflex/components/el/elements/__init__.py +3 -1
- reflex/components/el/elements/__init__.pyi +3 -2
- reflex/components/el/elements/base.pyi +7 -3
- reflex/components/el/elements/forms.py +1 -1
- reflex/components/el/elements/forms.pyi +72 -16
- reflex/components/el/elements/inline.pyi +142 -30
- reflex/components/el/elements/media.pyi +127 -27
- reflex/components/el/elements/metadata.pyi +32 -8
- reflex/components/el/elements/other.pyi +37 -9
- reflex/components/el/elements/scripts.pyi +17 -5
- reflex/components/el/elements/sectioning.pyi +77 -17
- reflex/components/el/elements/tables.pyi +52 -12
- reflex/components/el/elements/typography.pyi +77 -17
- reflex/components/gridjs/datatable.py +2 -2
- reflex/components/gridjs/datatable.pyi +12 -4
- reflex/components/lucide/icon.py +7 -6
- reflex/components/lucide/icon.pyi +22 -7
- reflex/components/markdown/markdown.py +1 -1
- reflex/components/markdown/markdown.pyi +7 -3
- reflex/components/moment/moment.pyi +7 -3
- reflex/components/next/base.pyi +7 -3
- reflex/components/next/image.pyi +7 -3
- reflex/components/next/link.pyi +7 -3
- reflex/components/next/video.pyi +7 -3
- reflex/components/plotly/plotly.pyi +47 -11
- reflex/components/radix/primitives/accordion.pyi +37 -9
- reflex/components/radix/primitives/base.pyi +12 -4
- reflex/components/radix/primitives/drawer.pyi +57 -13
- reflex/components/radix/primitives/form.pyi +52 -12
- reflex/components/radix/primitives/progress.pyi +27 -7
- reflex/components/radix/primitives/slider.pyi +27 -7
- reflex/components/radix/themes/base.pyi +41 -10
- reflex/components/radix/themes/color_mode.py +2 -2
- reflex/components/radix/themes/color_mode.pyi +17 -5
- reflex/components/radix/themes/components/alert_dialog.pyi +36 -9
- reflex/components/radix/themes/components/aspect_ratio.pyi +7 -3
- reflex/components/radix/themes/components/avatar.pyi +6 -3
- reflex/components/radix/themes/components/badge.pyi +6 -3
- reflex/components/radix/themes/components/button.pyi +6 -3
- reflex/components/radix/themes/components/callout.pyi +26 -7
- reflex/components/radix/themes/components/card.pyi +6 -3
- reflex/components/radix/themes/components/checkbox.pyi +16 -5
- reflex/components/radix/themes/components/checkbox_cards.pyi +11 -4
- reflex/components/radix/themes/components/checkbox_group.pyi +11 -4
- reflex/components/radix/themes/components/context_menu.pyi +66 -15
- reflex/components/radix/themes/components/data_list.pyi +21 -6
- reflex/components/radix/themes/components/dialog.pyi +36 -9
- reflex/components/radix/themes/components/dropdown_menu.pyi +41 -10
- reflex/components/radix/themes/components/hover_card.pyi +21 -6
- reflex/components/radix/themes/components/icon_button.pyi +6 -3
- reflex/components/radix/themes/components/inset.pyi +6 -3
- reflex/components/radix/themes/components/popover.pyi +21 -6
- reflex/components/radix/themes/components/progress.pyi +6 -3
- reflex/components/radix/themes/components/radio.pyi +6 -3
- reflex/components/radix/themes/components/radio_cards.pyi +11 -4
- reflex/components/radix/themes/components/radio_group.py +6 -1
- reflex/components/radix/themes/components/radio_group.pyi +21 -6
- reflex/components/radix/themes/components/scroll_area.pyi +7 -3
- reflex/components/radix/themes/components/segmented_control.pyi +11 -4
- reflex/components/radix/themes/components/select.pyi +46 -11
- reflex/components/radix/themes/components/separator.pyi +6 -3
- reflex/components/radix/themes/components/skeleton.pyi +6 -3
- reflex/components/radix/themes/components/slider.pyi +6 -3
- reflex/components/radix/themes/components/spinner.pyi +6 -3
- reflex/components/radix/themes/components/switch.pyi +6 -3
- reflex/components/radix/themes/components/table.pyi +36 -9
- reflex/components/radix/themes/components/tabs.pyi +26 -7
- reflex/components/radix/themes/components/text_area.pyi +6 -3
- reflex/components/radix/themes/components/text_field.py +3 -2
- reflex/components/radix/themes/components/text_field.pyi +16 -5
- reflex/components/radix/themes/components/tooltip.pyi +7 -3
- reflex/components/radix/themes/layout/base.pyi +6 -3
- reflex/components/radix/themes/layout/box.pyi +7 -3
- reflex/components/radix/themes/layout/center.pyi +6 -3
- reflex/components/radix/themes/layout/container.pyi +6 -3
- reflex/components/radix/themes/layout/flex.pyi +6 -3
- reflex/components/radix/themes/layout/grid.pyi +6 -3
- reflex/components/radix/themes/layout/list.pyi +27 -7
- reflex/components/radix/themes/layout/section.pyi +6 -3
- reflex/components/radix/themes/layout/spacer.pyi +6 -3
- reflex/components/radix/themes/layout/stack.pyi +16 -5
- reflex/components/radix/themes/typography/blockquote.pyi +6 -3
- reflex/components/radix/themes/typography/code.pyi +6 -3
- reflex/components/radix/themes/typography/heading.pyi +6 -3
- reflex/components/radix/themes/typography/link.pyi +6 -3
- reflex/components/radix/themes/typography/text.pyi +36 -9
- reflex/components/react_player/audio.pyi +7 -3
- reflex/components/react_player/react_player.pyi +7 -3
- reflex/components/react_player/video.pyi +7 -3
- reflex/components/recharts/cartesian.pyi +97 -21
- reflex/components/recharts/charts.pyi +62 -14
- reflex/components/recharts/general.pyi +32 -8
- reflex/components/recharts/polar.py +1 -1
- reflex/components/recharts/polar.pyi +33 -9
- reflex/components/recharts/recharts.pyi +12 -4
- reflex/components/sonner/toast.pyi +7 -2
- reflex/components/suneditor/editor.pyi +7 -3
- reflex/config.py +15 -1
- reflex/constants/installer.py +22 -1
- reflex/custom_components/custom_components.py +12 -7
- reflex/event.py +26 -10
- reflex/experimental/__init__.py +17 -6
- reflex/experimental/layout.pyi +27 -7
- reflex/model.py +3 -3
- reflex/reflex.py +33 -18
- reflex/state.py +3 -3
- reflex/style.py +2 -2
- reflex/testing.py +17 -5
- reflex/utils/console.py +2 -3
- reflex/utils/exec.py +4 -0
- reflex/utils/imports.py +14 -7
- reflex/utils/prerequisites.py +72 -7
- reflex/utils/processes.py +52 -19
- reflex/utils/pyi_generator.py +66 -53
- reflex/utils/registry.py +5 -3
- reflex/utils/serializers.py +1 -2
- reflex/utils/types.py +4 -4
- reflex/vars/base.py +58 -22
- reflex/vars/number.py +23 -6
- reflex/vars/sequence.py +2 -0
- {reflex-0.7.4a3.dist-info → reflex-0.7.5.dist-info}/METADATA +2 -2
- {reflex-0.7.4a3.dist-info → reflex-0.7.5.dist-info}/RECORD +160 -160
- /reflex/{experimental → utils}/misc.py +0 -0
- {reflex-0.7.4a3.dist-info → reflex-0.7.5.dist-info}/WHEEL +0 -0
- {reflex-0.7.4a3.dist-info → reflex-0.7.5.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.4a3.dist-info → reflex-0.7.5.dist-info}/licenses/LICENSE +0 -0
reflex/reflex.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import atexit
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
7
8
|
|
|
8
9
|
import typer
|
|
9
10
|
import typer.core
|
|
@@ -429,7 +430,7 @@ def logout(
|
|
|
429
430
|
|
|
430
431
|
loglevel = loglevel or get_config().loglevel
|
|
431
432
|
|
|
432
|
-
logout(loglevel)
|
|
433
|
+
logout(_convert_reflex_loglevel_to_reflex_cli_loglevel(loglevel))
|
|
433
434
|
|
|
434
435
|
|
|
435
436
|
db_cli = typer.Typer()
|
|
@@ -581,7 +582,6 @@ def deploy(
|
|
|
581
582
|
),
|
|
582
583
|
):
|
|
583
584
|
"""Deploy the app to the Reflex hosting service."""
|
|
584
|
-
from reflex_cli.constants.base import LogLevel as HostingLogLevel
|
|
585
585
|
from reflex_cli.utils import dependency
|
|
586
586
|
from reflex_cli.v2 import cli as hosting_cli
|
|
587
587
|
from reflex_cli.v2.deployments import check_version
|
|
@@ -604,21 +604,6 @@ def deploy(
|
|
|
604
604
|
# Set the log level.
|
|
605
605
|
console.set_log_level(loglevel)
|
|
606
606
|
|
|
607
|
-
def convert_reflex_loglevel_to_reflex_cli_loglevel(
|
|
608
|
-
loglevel: constants.LogLevel,
|
|
609
|
-
) -> HostingLogLevel:
|
|
610
|
-
if loglevel == constants.LogLevel.DEBUG:
|
|
611
|
-
return HostingLogLevel.DEBUG
|
|
612
|
-
if loglevel == constants.LogLevel.INFO:
|
|
613
|
-
return HostingLogLevel.INFO
|
|
614
|
-
if loglevel == constants.LogLevel.WARNING:
|
|
615
|
-
return HostingLogLevel.WARNING
|
|
616
|
-
if loglevel == constants.LogLevel.ERROR:
|
|
617
|
-
return HostingLogLevel.ERROR
|
|
618
|
-
if loglevel == constants.LogLevel.CRITICAL:
|
|
619
|
-
return HostingLogLevel.CRITICAL
|
|
620
|
-
return HostingLogLevel.INFO
|
|
621
|
-
|
|
622
607
|
# Only check requirements if interactive.
|
|
623
608
|
# There is user interaction for requirements update.
|
|
624
609
|
if interactive:
|
|
@@ -652,7 +637,7 @@ def deploy(
|
|
|
652
637
|
envfile=envfile,
|
|
653
638
|
hostname=hostname,
|
|
654
639
|
interactive=interactive,
|
|
655
|
-
loglevel=
|
|
640
|
+
loglevel=_convert_reflex_loglevel_to_reflex_cli_loglevel(loglevel),
|
|
656
641
|
token=token,
|
|
657
642
|
project=project,
|
|
658
643
|
project_name=project_name,
|
|
@@ -676,6 +661,36 @@ def rename(
|
|
|
676
661
|
prerequisites.rename_app(new_name, loglevel)
|
|
677
662
|
|
|
678
663
|
|
|
664
|
+
if TYPE_CHECKING:
|
|
665
|
+
from reflex_cli.constants.base import LogLevel as HostingLogLevel
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def _convert_reflex_loglevel_to_reflex_cli_loglevel(
|
|
669
|
+
loglevel: constants.LogLevel,
|
|
670
|
+
) -> HostingLogLevel:
|
|
671
|
+
"""Convert a Reflex log level to a Reflex CLI log level.
|
|
672
|
+
|
|
673
|
+
Args:
|
|
674
|
+
loglevel: The Reflex log level to convert.
|
|
675
|
+
|
|
676
|
+
Returns:
|
|
677
|
+
The converted Reflex CLI log level.
|
|
678
|
+
"""
|
|
679
|
+
from reflex_cli.constants.base import LogLevel as HostingLogLevel
|
|
680
|
+
|
|
681
|
+
if loglevel == constants.LogLevel.DEBUG:
|
|
682
|
+
return HostingLogLevel.DEBUG
|
|
683
|
+
if loglevel == constants.LogLevel.INFO:
|
|
684
|
+
return HostingLogLevel.INFO
|
|
685
|
+
if loglevel == constants.LogLevel.WARNING:
|
|
686
|
+
return HostingLogLevel.WARNING
|
|
687
|
+
if loglevel == constants.LogLevel.ERROR:
|
|
688
|
+
return HostingLogLevel.ERROR
|
|
689
|
+
if loglevel == constants.LogLevel.CRITICAL:
|
|
690
|
+
return HostingLogLevel.CRITICAL
|
|
691
|
+
return HostingLogLevel.INFO
|
|
692
|
+
|
|
693
|
+
|
|
679
694
|
cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
|
|
680
695
|
cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")
|
|
681
696
|
cli.add_typer(
|
reflex/state.py
CHANGED
|
@@ -593,8 +593,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
593
593
|
if cls._item_is_event_handler(name, fn)
|
|
594
594
|
}
|
|
595
595
|
|
|
596
|
-
for
|
|
597
|
-
for name, value in
|
|
596
|
+
for mixin_cls in cls._mixins():
|
|
597
|
+
for name, value in mixin_cls.__dict__.items():
|
|
598
598
|
if name in cls.inherited_vars:
|
|
599
599
|
continue
|
|
600
600
|
if is_computed_var(value):
|
|
@@ -605,7 +605,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
605
605
|
cls.computed_vars[newcv._js_expr] = newcv
|
|
606
606
|
cls.vars[newcv._js_expr] = newcv
|
|
607
607
|
continue
|
|
608
|
-
if types.is_backend_base_variable(name,
|
|
608
|
+
if types.is_backend_base_variable(name, mixin_cls):
|
|
609
609
|
cls.backend_vars[name] = copy.deepcopy(value)
|
|
610
610
|
continue
|
|
611
611
|
if events.get(name) is not None:
|
reflex/style.py
CHANGED
|
@@ -237,10 +237,10 @@ def format_style_key(key: str) -> tuple[str, ...]:
|
|
|
237
237
|
EMPTY_VAR_DATA = VarData()
|
|
238
238
|
|
|
239
239
|
|
|
240
|
-
class Style(dict):
|
|
240
|
+
class Style(dict[str, Any]):
|
|
241
241
|
"""A style dictionary."""
|
|
242
242
|
|
|
243
|
-
def __init__(self, style_dict: dict | None = None, **kwargs):
|
|
243
|
+
def __init__(self, style_dict: dict[str, Any] | None = None, **kwargs):
|
|
244
244
|
"""Initialize the style.
|
|
245
245
|
|
|
246
246
|
Args:
|
reflex/testing.py
CHANGED
|
@@ -27,6 +27,7 @@ from typing import (
|
|
|
27
27
|
Callable,
|
|
28
28
|
Coroutine,
|
|
29
29
|
Optional,
|
|
30
|
+
Sequence,
|
|
30
31
|
Type,
|
|
31
32
|
TypeVar,
|
|
32
33
|
)
|
|
@@ -268,6 +269,10 @@ class AppHarness:
|
|
|
268
269
|
loglevel=reflex.constants.LogLevel.INFO,
|
|
269
270
|
)
|
|
270
271
|
self.app_module_path.write_text(source_code)
|
|
272
|
+
else:
|
|
273
|
+
# Just initialize the web folder.
|
|
274
|
+
with chdir(self.app_path):
|
|
275
|
+
reflex.utils.prerequisites.initialize_frontend_dependencies()
|
|
271
276
|
with chdir(self.app_path):
|
|
272
277
|
# ensure config and app are reloaded when testing different app
|
|
273
278
|
reflex.config.get_config(reload=True)
|
|
@@ -290,8 +295,10 @@ class AppHarness:
|
|
|
290
295
|
if self.app_instance and isinstance(
|
|
291
296
|
self.app_instance._state_manager, StateManagerRedis
|
|
292
297
|
):
|
|
298
|
+
if self.app_instance._state is None:
|
|
299
|
+
raise RuntimeError("State is not set.")
|
|
293
300
|
# Create our own redis connection for testing.
|
|
294
|
-
self.state_manager = StateManagerRedis.create(self.app_instance._state)
|
|
301
|
+
self.state_manager = StateManagerRedis.create(self.app_instance._state)
|
|
295
302
|
else:
|
|
296
303
|
self.state_manager = (
|
|
297
304
|
self.app_instance._state_manager if self.app_instance else None
|
|
@@ -455,6 +462,10 @@ class AppHarness:
|
|
|
455
462
|
|
|
456
463
|
def stop(self) -> None:
|
|
457
464
|
"""Stop the frontend and backend servers."""
|
|
465
|
+
# Quit browsers first to avoid any lingering events being sent during shutdown.
|
|
466
|
+
for driver in self._frontends:
|
|
467
|
+
driver.quit()
|
|
468
|
+
|
|
458
469
|
self._reload_state_module()
|
|
459
470
|
|
|
460
471
|
if self.backend is not None:
|
|
@@ -485,8 +496,6 @@ class AppHarness:
|
|
|
485
496
|
self.backend_thread.join()
|
|
486
497
|
if self.frontend_output_thread is not None:
|
|
487
498
|
self.frontend_output_thread.join()
|
|
488
|
-
for driver in self._frontends:
|
|
489
|
-
driver.quit()
|
|
490
499
|
|
|
491
500
|
# Cleanup decorated pages added during testing
|
|
492
501
|
for page in self._decorated_pages:
|
|
@@ -764,7 +773,7 @@ class AppHarness:
|
|
|
764
773
|
self,
|
|
765
774
|
element: "WebElement",
|
|
766
775
|
timeout: TimeoutType = None,
|
|
767
|
-
exp_not_equal: str = "",
|
|
776
|
+
exp_not_equal: str | Sequence[str] = "",
|
|
768
777
|
) -> str | None:
|
|
769
778
|
"""Poll element.get_attribute("value") for change.
|
|
770
779
|
|
|
@@ -779,8 +788,11 @@ class AppHarness:
|
|
|
779
788
|
Raises:
|
|
780
789
|
TimeoutError: when the timeout expires before value changes
|
|
781
790
|
"""
|
|
791
|
+
exp_not_equal = (
|
|
792
|
+
(exp_not_equal,) if isinstance(exp_not_equal, str) else exp_not_equal
|
|
793
|
+
)
|
|
782
794
|
if not self._poll_for(
|
|
783
|
-
target=lambda: element.get_attribute("value")
|
|
795
|
+
target=lambda: element.get_attribute("value") not in exp_not_equal,
|
|
784
796
|
timeout=timeout,
|
|
785
797
|
):
|
|
786
798
|
raise TimeoutError(
|
reflex/utils/console.py
CHANGED
|
@@ -201,10 +201,9 @@ def _get_first_non_framework_frame() -> FrameType | None:
|
|
|
201
201
|
# Exclude utility modules that should never be the source of deprecated reflex usage.
|
|
202
202
|
exclude_modules = [click, rx, typer, typing_extensions]
|
|
203
203
|
exclude_roots = [
|
|
204
|
-
p.parent.resolve()
|
|
205
|
-
if (p := Path(m.__file__)).name == "__init__.py" # pyright: ignore [reportArgumentType]
|
|
206
|
-
else p.resolve()
|
|
204
|
+
p.parent.resolve() if (p := Path(file)).name == "__init__.py" else p.resolve()
|
|
207
205
|
for m in exclude_modules
|
|
206
|
+
if (file := m.__file__)
|
|
208
207
|
]
|
|
209
208
|
# Specifically exclude the reflex cli module.
|
|
210
209
|
if reflex_bin := shutil.which(b"reflex"):
|
reflex/utils/exec.py
CHANGED
|
@@ -330,6 +330,7 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
330
330
|
log_level=loglevel.value,
|
|
331
331
|
reload=True,
|
|
332
332
|
reload_dirs=list(map(str, get_reload_paths())),
|
|
333
|
+
reload_delay=0.1,
|
|
333
334
|
)
|
|
334
335
|
|
|
335
336
|
|
|
@@ -356,6 +357,9 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
356
357
|
log_level=LogLevels(loglevel.value),
|
|
357
358
|
reload=True,
|
|
358
359
|
reload_paths=get_reload_paths(),
|
|
360
|
+
reload_ignore_worker_failure=True,
|
|
361
|
+
reload_tick=100,
|
|
362
|
+
workers_kill_timeout=2,
|
|
359
363
|
).serve()
|
|
360
364
|
|
|
361
365
|
|
reflex/utils/imports.py
CHANGED
|
@@ -4,11 +4,11 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import dataclasses
|
|
6
6
|
from collections import defaultdict
|
|
7
|
-
from typing import DefaultDict, Union
|
|
7
|
+
from typing import DefaultDict, Mapping, Sequence, Union
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def merge_imports(
|
|
11
|
-
*imports: ImportDict | ParsedImportDict |
|
|
11
|
+
*imports: ImportDict | ParsedImportDict | ParsedImportTuple,
|
|
12
12
|
) -> ParsedImportDict:
|
|
13
13
|
"""Merge multiple import dicts together.
|
|
14
14
|
|
|
@@ -43,7 +43,9 @@ def merge_imports(
|
|
|
43
43
|
return all_imports
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
def parse_imports(
|
|
46
|
+
def parse_imports(
|
|
47
|
+
imports: ImmutableImportDict | ImmutableParsedImportDict,
|
|
48
|
+
) -> ParsedImportDict:
|
|
47
49
|
"""Parse the import dict into a standard format.
|
|
48
50
|
|
|
49
51
|
Args:
|
|
@@ -53,10 +55,12 @@ def parse_imports(imports: ImportDict | ParsedImportDict) -> ParsedImportDict:
|
|
|
53
55
|
The parsed import dict.
|
|
54
56
|
"""
|
|
55
57
|
|
|
56
|
-
def _make_list(
|
|
58
|
+
def _make_list(
|
|
59
|
+
value: ImmutableImportTypes,
|
|
60
|
+
) -> list[str | ImportVar] | list[ImportVar]:
|
|
57
61
|
if isinstance(value, (str, ImportVar)):
|
|
58
62
|
return [value]
|
|
59
|
-
return value
|
|
63
|
+
return list(value)
|
|
60
64
|
|
|
61
65
|
return {
|
|
62
66
|
package: [
|
|
@@ -68,7 +72,7 @@ def parse_imports(imports: ImportDict | ParsedImportDict) -> ParsedImportDict:
|
|
|
68
72
|
|
|
69
73
|
|
|
70
74
|
def collapse_imports(
|
|
71
|
-
imports: ParsedImportDict |
|
|
75
|
+
imports: ParsedImportDict | ParsedImportTuple,
|
|
72
76
|
) -> ParsedImportDict:
|
|
73
77
|
"""Remove all duplicate ImportVar within an ImportDict.
|
|
74
78
|
|
|
@@ -132,6 +136,9 @@ class ImportVar:
|
|
|
132
136
|
|
|
133
137
|
|
|
134
138
|
ImportTypes = Union[str, ImportVar, list[str | ImportVar], list[ImportVar]]
|
|
139
|
+
ImmutableImportTypes = Union[str, ImportVar, Sequence[str | ImportVar]]
|
|
135
140
|
ImportDict = dict[str, ImportTypes]
|
|
141
|
+
ImmutableImportDict = Mapping[str, ImmutableImportTypes]
|
|
136
142
|
ParsedImportDict = dict[str, list[ImportVar]]
|
|
137
|
-
ImmutableParsedImportDict =
|
|
143
|
+
ImmutableParsedImportDict = Mapping[str, Sequence[ImportVar]]
|
|
144
|
+
ParsedImportTuple = tuple[tuple[str, tuple[ImportVar, ...]], ...]
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -510,6 +510,9 @@ def compile_or_validate_app(compile: bool = False) -> bool:
|
|
|
510
510
|
else:
|
|
511
511
|
validate_app()
|
|
512
512
|
except Exception as e:
|
|
513
|
+
if isinstance(e, typer.Exit):
|
|
514
|
+
return False
|
|
515
|
+
|
|
513
516
|
import traceback
|
|
514
517
|
|
|
515
518
|
sys_exception = sys.exception()
|
|
@@ -963,6 +966,9 @@ def initialize_web_directory():
|
|
|
963
966
|
console.debug("Initializing the bun config file.")
|
|
964
967
|
initialize_bun_config()
|
|
965
968
|
|
|
969
|
+
console.debug("Initializing the .npmrc file.")
|
|
970
|
+
initialize_npmrc()
|
|
971
|
+
|
|
966
972
|
console.debug("Initializing the public directory.")
|
|
967
973
|
path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
|
|
968
974
|
|
|
@@ -1012,6 +1018,20 @@ def initialize_bun_config():
|
|
|
1012
1018
|
bun_config_path.write_text(bunfig_content)
|
|
1013
1019
|
|
|
1014
1020
|
|
|
1021
|
+
def initialize_npmrc():
|
|
1022
|
+
"""Initialize the .npmrc file."""
|
|
1023
|
+
npmrc_path = get_web_dir() / constants.Node.CONFIG_PATH
|
|
1024
|
+
|
|
1025
|
+
if (custom_npmrc := Path(constants.Node.CONFIG_PATH)).exists():
|
|
1026
|
+
npmrc_content = custom_npmrc.read_text()
|
|
1027
|
+
console.info(f"Copying custom .npmrc inside {get_web_dir()} folder")
|
|
1028
|
+
else:
|
|
1029
|
+
best_registry = get_npm_registry()
|
|
1030
|
+
npmrc_content = constants.Node.DEFAULT_CONFIG.format(registry=best_registry)
|
|
1031
|
+
|
|
1032
|
+
npmrc_path.write_text(npmrc_content)
|
|
1033
|
+
|
|
1034
|
+
|
|
1015
1035
|
def init_reflex_json(project_hash: int | None):
|
|
1016
1036
|
"""Write the hash of the Reflex project to a REFLEX_JSON.
|
|
1017
1037
|
|
|
@@ -1067,6 +1087,7 @@ def _update_next_config(
|
|
|
1067
1087
|
"compress": config.next_compression,
|
|
1068
1088
|
"trailingSlash": True,
|
|
1069
1089
|
"staticPageGenerationTimeout": config.static_page_generation_timeout,
|
|
1090
|
+
"devIndicators": config.next_dev_indicators,
|
|
1070
1091
|
}
|
|
1071
1092
|
if transpile_packages:
|
|
1072
1093
|
next_config["transpilePackages"] = list(
|
|
@@ -1180,26 +1201,39 @@ def _clear_cached_procedure_file(cache_file: str | Path):
|
|
|
1180
1201
|
cache_file.unlink()
|
|
1181
1202
|
|
|
1182
1203
|
|
|
1183
|
-
def cached_procedure(
|
|
1204
|
+
def cached_procedure(
|
|
1205
|
+
cache_file: str | None,
|
|
1206
|
+
payload_fn: Callable[..., str],
|
|
1207
|
+
cache_file_fn: Callable[[], str] | None = None,
|
|
1208
|
+
):
|
|
1184
1209
|
"""Decorator to cache the runs of a procedure on disk. Procedures should not have
|
|
1185
1210
|
a return value.
|
|
1186
1211
|
|
|
1187
1212
|
Args:
|
|
1188
1213
|
cache_file: The file to store the cache payload in.
|
|
1189
|
-
payload_fn: Function that computes cache payload from function args
|
|
1214
|
+
payload_fn: Function that computes cache payload from function args.
|
|
1215
|
+
cache_file_fn: Function that computes the cache file name at runtime.
|
|
1190
1216
|
|
|
1191
1217
|
Returns:
|
|
1192
1218
|
The decorated function.
|
|
1219
|
+
|
|
1220
|
+
Raises:
|
|
1221
|
+
ValueError: If both cache_file and cache_file_fn are provided.
|
|
1193
1222
|
"""
|
|
1223
|
+
if cache_file and cache_file_fn is not None:
|
|
1224
|
+
raise ValueError("cache_file and cache_file_fn cannot both be provided.")
|
|
1194
1225
|
|
|
1195
1226
|
def _inner_decorator(func: Callable):
|
|
1196
1227
|
def _inner(*args, **kwargs):
|
|
1197
|
-
|
|
1228
|
+
_cache_file = cache_file_fn() if cache_file_fn is not None else cache_file
|
|
1229
|
+
if not _cache_file:
|
|
1230
|
+
raise ValueError("Unknown cache file, cannot cache result.")
|
|
1231
|
+
payload = _read_cached_procedure_file(_cache_file)
|
|
1198
1232
|
new_payload = payload_fn(*args, **kwargs)
|
|
1199
1233
|
if payload != new_payload:
|
|
1200
|
-
_clear_cached_procedure_file(
|
|
1234
|
+
_clear_cached_procedure_file(_cache_file)
|
|
1201
1235
|
func(*args, **kwargs)
|
|
1202
|
-
_write_cached_procedure_file(new_payload,
|
|
1236
|
+
_write_cached_procedure_file(new_payload, _cache_file)
|
|
1203
1237
|
|
|
1204
1238
|
return _inner
|
|
1205
1239
|
|
|
@@ -1207,8 +1241,11 @@ def cached_procedure(cache_file: str, payload_fn: Callable[..., str]):
|
|
|
1207
1241
|
|
|
1208
1242
|
|
|
1209
1243
|
@cached_procedure(
|
|
1210
|
-
|
|
1244
|
+
cache_file_fn=lambda: str(
|
|
1245
|
+
get_web_dir() / "reflex.install_frontend_packages.cached"
|
|
1246
|
+
),
|
|
1211
1247
|
payload_fn=lambda p, c: f"{sorted(p)!r},{c.json()}",
|
|
1248
|
+
cache_file=None,
|
|
1212
1249
|
)
|
|
1213
1250
|
def install_frontend_packages(packages: set[str], config: Config):
|
|
1214
1251
|
"""Installs the base and custom frontend packages.
|
|
@@ -1224,6 +1261,14 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1224
1261
|
raise_on_none=True
|
|
1225
1262
|
)
|
|
1226
1263
|
|
|
1264
|
+
env = (
|
|
1265
|
+
{
|
|
1266
|
+
"NODE_TLS_REJECT_UNAUTHORIZED": "0",
|
|
1267
|
+
}
|
|
1268
|
+
if environment.SSL_NO_VERIFY.get()
|
|
1269
|
+
else {}
|
|
1270
|
+
)
|
|
1271
|
+
|
|
1227
1272
|
primary_package_manager = install_package_managers[0]
|
|
1228
1273
|
fallbacks = install_package_managers[1:]
|
|
1229
1274
|
|
|
@@ -1234,6 +1279,7 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1234
1279
|
show_status_message="Installing base frontend packages",
|
|
1235
1280
|
cwd=get_web_dir(),
|
|
1236
1281
|
shell=constants.IS_WINDOWS,
|
|
1282
|
+
env=env,
|
|
1237
1283
|
)
|
|
1238
1284
|
|
|
1239
1285
|
if config.tailwind is not None:
|
|
@@ -1251,6 +1297,7 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1251
1297
|
show_status_message="Installing tailwind",
|
|
1252
1298
|
cwd=get_web_dir(),
|
|
1253
1299
|
shell=constants.IS_WINDOWS,
|
|
1300
|
+
env=env,
|
|
1254
1301
|
)
|
|
1255
1302
|
|
|
1256
1303
|
# Install custom packages defined in frontend_packages
|
|
@@ -1262,6 +1309,7 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1262
1309
|
show_status_message="Installing frontend packages from config and components",
|
|
1263
1310
|
cwd=get_web_dir(),
|
|
1264
1311
|
shell=constants.IS_WINDOWS,
|
|
1312
|
+
env=env,
|
|
1265
1313
|
)
|
|
1266
1314
|
|
|
1267
1315
|
|
|
@@ -1510,6 +1558,9 @@ def prompt_for_template_options(templates: list[Template]) -> str:
|
|
|
1510
1558
|
|
|
1511
1559
|
Returns:
|
|
1512
1560
|
The template name the user selects.
|
|
1561
|
+
|
|
1562
|
+
Raises:
|
|
1563
|
+
Exit: If the user does not select a template.
|
|
1513
1564
|
"""
|
|
1514
1565
|
# Show the user the URLs of each template to preview.
|
|
1515
1566
|
console.print("\nGet started with a template:")
|
|
@@ -1534,8 +1585,22 @@ def prompt_for_template_options(templates: list[Template]) -> str:
|
|
|
1534
1585
|
default="0",
|
|
1535
1586
|
)
|
|
1536
1587
|
|
|
1588
|
+
if not template:
|
|
1589
|
+
console.error("No template selected.")
|
|
1590
|
+
raise typer.Exit(1)
|
|
1591
|
+
|
|
1592
|
+
try:
|
|
1593
|
+
template_index = int(template)
|
|
1594
|
+
except ValueError:
|
|
1595
|
+
console.error("Invalid template selected.")
|
|
1596
|
+
raise typer.Exit(1) from None
|
|
1597
|
+
|
|
1598
|
+
if template_index < 0 or template_index >= len(templates):
|
|
1599
|
+
console.error("Invalid template selected.")
|
|
1600
|
+
raise typer.Exit(1)
|
|
1601
|
+
|
|
1537
1602
|
# Return the template.
|
|
1538
|
-
return templates[
|
|
1603
|
+
return templates[template_index].name
|
|
1539
1604
|
|
|
1540
1605
|
|
|
1541
1606
|
def fetch_app_templates(version: str) -> dict[str, Template]:
|
reflex/utils/processes.py
CHANGED
|
@@ -20,6 +20,7 @@ from rich.progress import Progress
|
|
|
20
20
|
from reflex import constants
|
|
21
21
|
from reflex.config import environment
|
|
22
22
|
from reflex.utils import console, path_ops, prerequisites
|
|
23
|
+
from reflex.utils.registry import get_npm_registry
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
def kill(pid: int):
|
|
@@ -276,6 +277,7 @@ def stream_logs(
|
|
|
276
277
|
progress: Progress | None = None,
|
|
277
278
|
suppress_errors: bool = False,
|
|
278
279
|
analytics_enabled: bool = False,
|
|
280
|
+
prior_logs: Tuple[tuple[str, ...], ...] = (),
|
|
279
281
|
):
|
|
280
282
|
"""Stream the logs for a process.
|
|
281
283
|
|
|
@@ -285,6 +287,7 @@ def stream_logs(
|
|
|
285
287
|
progress: The ongoing progress bar if one is being used.
|
|
286
288
|
suppress_errors: If True, do not exit if errors are encountered (for fallback).
|
|
287
289
|
analytics_enabled: Whether analytics are enabled for this command.
|
|
290
|
+
prior_logs: The logs of the prior processes that have been run.
|
|
288
291
|
|
|
289
292
|
Yields:
|
|
290
293
|
The lines of the process output.
|
|
@@ -312,8 +315,31 @@ def stream_logs(
|
|
|
312
315
|
accepted_return_codes = [0, -2, 15] if constants.IS_WINDOWS else [0, -2]
|
|
313
316
|
if process.returncode not in accepted_return_codes and not suppress_errors:
|
|
314
317
|
console.error(f"{message} failed with exit code {process.returncode}")
|
|
315
|
-
|
|
316
|
-
|
|
318
|
+
if "".join(logs).count("CERT_HAS_EXPIRED") > 0:
|
|
319
|
+
bunfig = prerequisites.get_web_dir() / constants.Bun.CONFIG_PATH
|
|
320
|
+
npm_registry_line = next(
|
|
321
|
+
(
|
|
322
|
+
line
|
|
323
|
+
for line in bunfig.read_text().splitlines()
|
|
324
|
+
if line.startswith("registry")
|
|
325
|
+
),
|
|
326
|
+
None,
|
|
327
|
+
)
|
|
328
|
+
if not npm_registry_line or "=" not in npm_registry_line:
|
|
329
|
+
npm_registry = get_npm_registry()
|
|
330
|
+
else:
|
|
331
|
+
npm_registry = npm_registry_line.split("=")[1].strip()
|
|
332
|
+
console.error(
|
|
333
|
+
f"Failed to fetch securely from [bold]{npm_registry}[/bold]. Please check your network connection. "
|
|
334
|
+
"You can try running the command again or changing the registry by setting the "
|
|
335
|
+
"NPM_CONFIG_REGISTRY environment variable. If TLS is the issue, and you know what "
|
|
336
|
+
"you are doing, you can disable it by setting the SSL_NO_VERIFY environment variable."
|
|
337
|
+
)
|
|
338
|
+
raise typer.Exit(1)
|
|
339
|
+
for set_of_logs in (*prior_logs, tuple(logs)):
|
|
340
|
+
for line in set_of_logs:
|
|
341
|
+
console.error(line, end="")
|
|
342
|
+
console.error("\n\n")
|
|
317
343
|
if analytics_enabled:
|
|
318
344
|
telemetry.send("error", context=message)
|
|
319
345
|
console.error("Run with [bold]--loglevel debug [/bold] for the full log.")
|
|
@@ -336,8 +362,8 @@ def show_status(
|
|
|
336
362
|
process: subprocess.Popen,
|
|
337
363
|
suppress_errors: bool = False,
|
|
338
364
|
analytics_enabled: bool = False,
|
|
339
|
-
|
|
340
|
-
):
|
|
365
|
+
prior_logs: Tuple[tuple[str, ...], ...] = (),
|
|
366
|
+
) -> list[str]:
|
|
341
367
|
"""Show the status of a process.
|
|
342
368
|
|
|
343
369
|
Args:
|
|
@@ -345,17 +371,24 @@ def show_status(
|
|
|
345
371
|
process: The process.
|
|
346
372
|
suppress_errors: If True, do not exit if errors are encountered (for fallback).
|
|
347
373
|
analytics_enabled: Whether analytics are enabled for this command.
|
|
348
|
-
|
|
374
|
+
prior_logs: The logs of the prior processes that have been run.
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
The lines of the process output.
|
|
349
378
|
"""
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
379
|
+
lines = []
|
|
380
|
+
|
|
381
|
+
with console.status(message) as status:
|
|
382
|
+
for line in stream_logs(
|
|
383
|
+
message,
|
|
384
|
+
process,
|
|
385
|
+
suppress_errors=suppress_errors,
|
|
386
|
+
analytics_enabled=analytics_enabled,
|
|
387
|
+
prior_logs=prior_logs,
|
|
388
|
+
):
|
|
389
|
+
status.update(f"{message} {line}")
|
|
390
|
+
lines.append(line)
|
|
391
|
+
return lines
|
|
359
392
|
|
|
360
393
|
|
|
361
394
|
def show_progress(message: str, process: subprocess.Popen, checkpoints: list[str]):
|
|
@@ -409,7 +442,7 @@ def run_process_with_fallbacks(
|
|
|
409
442
|
show_status_message: str,
|
|
410
443
|
fallbacks: str | Sequence[str] | Sequence[Sequence[str]] | None = None,
|
|
411
444
|
analytics_enabled: bool = False,
|
|
412
|
-
|
|
445
|
+
prior_logs: Tuple[tuple[str, ...], ...] = (),
|
|
413
446
|
**kwargs,
|
|
414
447
|
):
|
|
415
448
|
"""Run subprocess and retry using fallback command if initial command fails.
|
|
@@ -419,7 +452,7 @@ def run_process_with_fallbacks(
|
|
|
419
452
|
show_status_message: The status message to be displayed in the console.
|
|
420
453
|
fallbacks: The fallback command to run if the initial command fails.
|
|
421
454
|
analytics_enabled: Whether analytics are enabled for this command.
|
|
422
|
-
|
|
455
|
+
prior_logs: The logs of the prior processes that have been run.
|
|
423
456
|
**kwargs: Kwargs to pass to new_process function.
|
|
424
457
|
"""
|
|
425
458
|
process = new_process(get_command_with_loglevel(args), **kwargs)
|
|
@@ -429,11 +462,11 @@ def run_process_with_fallbacks(
|
|
|
429
462
|
show_status_message,
|
|
430
463
|
process,
|
|
431
464
|
analytics_enabled=analytics_enabled,
|
|
432
|
-
|
|
465
|
+
prior_logs=prior_logs,
|
|
433
466
|
)
|
|
434
467
|
else:
|
|
435
468
|
# Suppress errors for initial command, because we will try to fallback
|
|
436
|
-
show_status(show_status_message, process, suppress_errors=True)
|
|
469
|
+
logs = show_status(show_status_message, process, suppress_errors=True)
|
|
437
470
|
|
|
438
471
|
current_fallback = fallbacks[0] if not isinstance(fallbacks, str) else fallbacks
|
|
439
472
|
next_fallbacks = fallbacks[1:] if not isinstance(fallbacks, str) else None
|
|
@@ -453,7 +486,7 @@ def run_process_with_fallbacks(
|
|
|
453
486
|
show_status_message=show_status_message,
|
|
454
487
|
fallbacks=next_fallbacks,
|
|
455
488
|
analytics_enabled=analytics_enabled,
|
|
456
|
-
|
|
489
|
+
prior_logs=(*prior_logs, tuple(logs)),
|
|
457
490
|
**kwargs,
|
|
458
491
|
)
|
|
459
492
|
|