reflex 0.6.5a3__py3-none-any.whl → 0.6.6a1__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/web/utils/state.js +15 -1
- reflex/__init__.py +3 -1
- reflex/__init__.pyi +3 -0
- reflex/app.py +4 -2
- reflex/assets.py +95 -0
- reflex/base.py +2 -2
- reflex/components/base/error_boundary.py +99 -36
- reflex/components/base/error_boundary.pyi +3 -4
- reflex/components/component.py +29 -7
- reflex/components/core/cond.py +8 -0
- reflex/components/datadisplay/code.py +1 -1
- reflex/components/datadisplay/logo.py +26 -13
- reflex/components/el/__init__.pyi +2 -0
- reflex/components/el/elements/__init__.py +1 -0
- reflex/components/el/elements/__init__.pyi +3 -0
- reflex/components/el/elements/forms.py +1 -0
- reflex/components/el/elements/forms.pyi +1 -0
- reflex/components/moment/moment.py +4 -3
- reflex/components/moment/moment.pyi +12 -2
- reflex/components/radix/primitives/drawer.py +5 -23
- reflex/components/radix/themes/base.py +3 -0
- reflex/components/radix/themes/components/segmented_control.py +3 -1
- reflex/components/radix/themes/components/segmented_control.pyi +7 -2
- reflex/components/radix/themes/components/text_field.py +3 -0
- reflex/components/radix/themes/components/text_field.pyi +4 -0
- reflex/components/sonner/toast.py +23 -12
- reflex/components/sonner/toast.pyi +6 -6
- reflex/config.py +60 -9
- reflex/constants/base.py +12 -0
- reflex/constants/installer.py +3 -3
- reflex/constants/style.py +1 -1
- reflex/event.py +22 -5
- reflex/experimental/assets.py +14 -36
- reflex/reflex.py +15 -34
- reflex/state.py +81 -23
- reflex/utils/exceptions.py +4 -0
- reflex/utils/prerequisites.py +174 -40
- reflex/utils/redir.py +13 -4
- reflex/utils/serializers.py +52 -1
- reflex/utils/telemetry.py +2 -1
- reflex/utils/types.py +52 -1
- reflex/vars/base.py +18 -4
- reflex/vars/function.py +283 -37
- {reflex-0.6.5a3.dist-info → reflex-0.6.6a1.dist-info}/METADATA +3 -2
- {reflex-0.6.5a3.dist-info → reflex-0.6.6a1.dist-info}/RECORD +48 -47
- {reflex-0.6.5a3.dist-info → reflex-0.6.6a1.dist-info}/LICENSE +0 -0
- {reflex-0.6.5a3.dist-info → reflex-0.6.6a1.dist-info}/WHEEL +0 -0
- {reflex-0.6.5a3.dist-info → reflex-0.6.6a1.dist-info}/entry_points.txt +0 -0
reflex/reflex.py
CHANGED
|
@@ -11,16 +11,16 @@ import typer
|
|
|
11
11
|
import typer.core
|
|
12
12
|
from reflex_cli.deployments import deployments_cli
|
|
13
13
|
from reflex_cli.utils import dependency
|
|
14
|
-
from reflex_cli.v2.deployments import hosting_cli
|
|
14
|
+
from reflex_cli.v2.deployments import check_version, hosting_cli
|
|
15
15
|
|
|
16
16
|
from reflex import constants
|
|
17
17
|
from reflex.config import environment, get_config
|
|
18
18
|
from reflex.custom_components.custom_components import custom_components_cli
|
|
19
19
|
from reflex.state import reset_disk_state_manager
|
|
20
|
-
from reflex.utils import console,
|
|
20
|
+
from reflex.utils import console, telemetry
|
|
21
21
|
|
|
22
22
|
# Disable typer+rich integration for help panels
|
|
23
|
-
typer.core.rich =
|
|
23
|
+
typer.core.rich = None # type: ignore
|
|
24
24
|
|
|
25
25
|
# Create the app.
|
|
26
26
|
try:
|
|
@@ -89,30 +89,8 @@ def _init(
|
|
|
89
89
|
# Set up the web project.
|
|
90
90
|
prerequisites.initialize_frontend_dependencies()
|
|
91
91
|
|
|
92
|
-
# Integrate with reflex.build.
|
|
93
|
-
generation_hash = None
|
|
94
|
-
if ai:
|
|
95
|
-
if template is None:
|
|
96
|
-
# If AI is requested and no template specified, redirect the user to reflex.build.
|
|
97
|
-
generation_hash = redir.reflex_build_redirect()
|
|
98
|
-
elif prerequisites.is_generation_hash(template):
|
|
99
|
-
# Otherwise treat the template as a generation hash.
|
|
100
|
-
generation_hash = template
|
|
101
|
-
else:
|
|
102
|
-
console.error(
|
|
103
|
-
"Cannot use `--template` option with `--ai` option. Please remove `--template` option."
|
|
104
|
-
)
|
|
105
|
-
raise typer.Exit(2)
|
|
106
|
-
template = constants.Templates.DEFAULT
|
|
107
|
-
|
|
108
92
|
# Initialize the app.
|
|
109
|
-
prerequisites.initialize_app(app_name, template)
|
|
110
|
-
|
|
111
|
-
# If a reflex.build generation hash is available, download the code and apply it to the main module.
|
|
112
|
-
if generation_hash:
|
|
113
|
-
prerequisites.initialize_main_module_index_from_generation(
|
|
114
|
-
app_name, generation_hash=generation_hash
|
|
115
|
-
)
|
|
93
|
+
template = prerequisites.initialize_app(app_name, template, ai)
|
|
116
94
|
|
|
117
95
|
# Initialize the .gitignore.
|
|
118
96
|
prerequisites.initialize_gitignore()
|
|
@@ -120,8 +98,9 @@ def _init(
|
|
|
120
98
|
# Initialize the requirements.txt.
|
|
121
99
|
prerequisites.initialize_requirements_txt()
|
|
122
100
|
|
|
101
|
+
template_msg = f" using the {template} template" if template else ""
|
|
123
102
|
# Finish initializing the app.
|
|
124
|
-
console.success(f"Initialized {app_name}")
|
|
103
|
+
console.success(f"Initialized {app_name}{template_msg}")
|
|
125
104
|
|
|
126
105
|
|
|
127
106
|
@cli.command()
|
|
@@ -389,6 +368,8 @@ def loginv2(loglevel: constants.LogLevel = typer.Option(config.loglevel)):
|
|
|
389
368
|
"""Authenicate with experimental Reflex hosting service."""
|
|
390
369
|
from reflex_cli.v2 import cli as hosting_cli
|
|
391
370
|
|
|
371
|
+
check_version()
|
|
372
|
+
|
|
392
373
|
hosting_cli.login()
|
|
393
374
|
|
|
394
375
|
|
|
@@ -417,6 +398,8 @@ def logoutv2(
|
|
|
417
398
|
"""Log out of access to Reflex hosting service."""
|
|
418
399
|
from reflex_cli.v2.utils import hosting
|
|
419
400
|
|
|
401
|
+
check_version()
|
|
402
|
+
|
|
420
403
|
console.set_log_level(loglevel)
|
|
421
404
|
|
|
422
405
|
hosting.log_out_on_browser()
|
|
@@ -636,7 +619,7 @@ def deployv2(
|
|
|
636
619
|
list(),
|
|
637
620
|
"-r",
|
|
638
621
|
"--region",
|
|
639
|
-
help="The regions to deploy to. For multiple envs, repeat this option, e.g. --region sjc --region iad",
|
|
622
|
+
help="The regions to deploy to. `reflex apps regions` For multiple envs, repeat this option, e.g. --region sjc --region iad",
|
|
640
623
|
),
|
|
641
624
|
envs: List[str] = typer.Option(
|
|
642
625
|
list(),
|
|
@@ -646,13 +629,12 @@ def deployv2(
|
|
|
646
629
|
vmtype: Optional[str] = typer.Option(
|
|
647
630
|
None,
|
|
648
631
|
"--vmtype",
|
|
649
|
-
help="Vm type id. Run reflex apps vmtypes
|
|
632
|
+
help="Vm type id. Run `reflex apps vmtypes` to get options.",
|
|
650
633
|
),
|
|
651
634
|
hostname: Optional[str] = typer.Option(
|
|
652
635
|
None,
|
|
653
636
|
"--hostname",
|
|
654
637
|
help="The hostname of the frontend.",
|
|
655
|
-
hidden=True,
|
|
656
638
|
),
|
|
657
639
|
interactive: bool = typer.Option(
|
|
658
640
|
True,
|
|
@@ -662,7 +644,6 @@ def deployv2(
|
|
|
662
644
|
None,
|
|
663
645
|
"--envfile",
|
|
664
646
|
help="The path to an env file to use. Will override any envs set manually.",
|
|
665
|
-
hidden=True,
|
|
666
647
|
),
|
|
667
648
|
loglevel: constants.LogLevel = typer.Option(
|
|
668
649
|
config.loglevel, help="The log level to use."
|
|
@@ -670,14 +651,12 @@ def deployv2(
|
|
|
670
651
|
project: Optional[str] = typer.Option(
|
|
671
652
|
None,
|
|
672
653
|
"--project",
|
|
673
|
-
help="project to deploy to",
|
|
674
|
-
hidden=True,
|
|
654
|
+
help="project id to deploy to",
|
|
675
655
|
),
|
|
676
656
|
token: Optional[str] = typer.Option(
|
|
677
657
|
None,
|
|
678
658
|
"--token",
|
|
679
659
|
help="token to use for auth",
|
|
680
|
-
hidden=True,
|
|
681
660
|
),
|
|
682
661
|
):
|
|
683
662
|
"""Deploy the app to the Reflex hosting service."""
|
|
@@ -687,6 +666,8 @@ def deployv2(
|
|
|
687
666
|
from reflex.utils import export as export_utils
|
|
688
667
|
from reflex.utils import prerequisites
|
|
689
668
|
|
|
669
|
+
check_version()
|
|
670
|
+
|
|
690
671
|
# Set the log level.
|
|
691
672
|
console.set_log_level(loglevel)
|
|
692
673
|
|
reflex/state.py
CHANGED
|
@@ -43,7 +43,7 @@ from sqlalchemy.orm import DeclarativeBase
|
|
|
43
43
|
from typing_extensions import Self
|
|
44
44
|
|
|
45
45
|
from reflex import event
|
|
46
|
-
from reflex.config import get_config
|
|
46
|
+
from reflex.config import PerformanceMode, get_config
|
|
47
47
|
from reflex.istate.data import RouterData
|
|
48
48
|
from reflex.istate.storage import ClientStorageBase
|
|
49
49
|
from reflex.model import Model
|
|
@@ -62,6 +62,13 @@ try:
|
|
|
62
62
|
except ModuleNotFoundError:
|
|
63
63
|
import pydantic
|
|
64
64
|
|
|
65
|
+
from pydantic import BaseModel as BaseModelV2
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
from pydantic.v1 import BaseModel as BaseModelV1
|
|
69
|
+
except ModuleNotFoundError:
|
|
70
|
+
BaseModelV1 = BaseModelV2
|
|
71
|
+
|
|
65
72
|
import wrapt
|
|
66
73
|
from redis.asyncio import Redis
|
|
67
74
|
from redis.exceptions import ResponseError
|
|
@@ -87,8 +94,10 @@ from reflex.utils.exceptions import (
|
|
|
87
94
|
ImmutableStateError,
|
|
88
95
|
InvalidStateManagerMode,
|
|
89
96
|
LockExpiredError,
|
|
97
|
+
ReflexRuntimeError,
|
|
90
98
|
SetUndefinedStateVarError,
|
|
91
99
|
StateSchemaMismatchError,
|
|
100
|
+
StateTooLargeError,
|
|
92
101
|
)
|
|
93
102
|
from reflex.utils.exec import is_testing_env
|
|
94
103
|
from reflex.utils.serializers import serializer
|
|
@@ -109,10 +118,11 @@ Delta = Dict[str, Any]
|
|
|
109
118
|
var = computed_var
|
|
110
119
|
|
|
111
120
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
|
|
122
|
+
# If the state is this large, it's considered a performance issue.
|
|
123
|
+
TOO_LARGE_SERIALIZED_STATE = environment.REFLEX_STATE_SIZE_LIMIT.get() * 1024
|
|
124
|
+
# Only warn about each state class size once.
|
|
125
|
+
_WARNED_ABOUT_STATE_SIZE: Set[str] = set()
|
|
116
126
|
|
|
117
127
|
# Errors caught during pickling of state
|
|
118
128
|
HANDLED_PICKLE_ERRORS = (
|
|
@@ -387,6 +397,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
387
397
|
"State classes should not be instantiated directly in a Reflex app. "
|
|
388
398
|
"See https://reflex.dev/docs/state/ for further information."
|
|
389
399
|
)
|
|
400
|
+
if type(self)._mixin:
|
|
401
|
+
raise ReflexRuntimeError(
|
|
402
|
+
f"{type(self).__name__} is a state mixin and cannot be instantiated directly."
|
|
403
|
+
)
|
|
390
404
|
kwargs["parent_state"] = parent_state
|
|
391
405
|
super().__init__()
|
|
392
406
|
for name, value in kwargs.items():
|
|
@@ -1243,7 +1257,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1243
1257
|
if parent_state is not None:
|
|
1244
1258
|
return getattr(parent_state, name)
|
|
1245
1259
|
|
|
1246
|
-
if
|
|
1260
|
+
if MutableProxy._is_mutable_type(value) and (
|
|
1247
1261
|
name in super().__getattribute__("base_vars") or name in backend_vars
|
|
1248
1262
|
):
|
|
1249
1263
|
# track changes in mutable containers (list, dict, set, etc)
|
|
@@ -1890,7 +1904,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1890
1904
|
)
|
|
1891
1905
|
|
|
1892
1906
|
subdelta: Dict[str, Any] = {
|
|
1893
|
-
prop: self.get_value(
|
|
1907
|
+
prop: self.get_value(prop)
|
|
1894
1908
|
for prop in delta_vars
|
|
1895
1909
|
if not types.is_backend_base_variable(prop, type(self))
|
|
1896
1910
|
}
|
|
@@ -1982,9 +1996,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1982
1996
|
Returns:
|
|
1983
1997
|
The value of the field.
|
|
1984
1998
|
"""
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1999
|
+
value = super().get_value(key)
|
|
2000
|
+
if isinstance(value, MutableProxy):
|
|
2001
|
+
return value.__wrapped__
|
|
2002
|
+
return value
|
|
1988
2003
|
|
|
1989
2004
|
def dict(
|
|
1990
2005
|
self, include_computed: bool = True, initial: bool = False, **kwargs
|
|
@@ -2006,8 +2021,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2006
2021
|
self._mark_dirty()
|
|
2007
2022
|
|
|
2008
2023
|
base_vars = {
|
|
2009
|
-
prop_name: self.get_value(
|
|
2010
|
-
for prop_name in self.base_vars
|
|
2024
|
+
prop_name: self.get_value(prop_name) for prop_name in self.base_vars
|
|
2011
2025
|
}
|
|
2012
2026
|
if initial and include_computed:
|
|
2013
2027
|
computed_vars = {
|
|
@@ -2016,7 +2030,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2016
2030
|
cv._initial_value
|
|
2017
2031
|
if is_computed_var(cv)
|
|
2018
2032
|
and not isinstance(cv._initial_value, types.Unset)
|
|
2019
|
-
else self.get_value(
|
|
2033
|
+
else self.get_value(prop_name)
|
|
2020
2034
|
)
|
|
2021
2035
|
for prop_name, cv in self.computed_vars.items()
|
|
2022
2036
|
if not cv._backend
|
|
@@ -2024,7 +2038,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2024
2038
|
elif include_computed:
|
|
2025
2039
|
computed_vars = {
|
|
2026
2040
|
# Include the computed vars.
|
|
2027
|
-
prop_name: self.get_value(
|
|
2041
|
+
prop_name: self.get_value(prop_name)
|
|
2028
2042
|
for prop_name, cv in self.computed_vars.items()
|
|
2029
2043
|
if not cv._backend
|
|
2030
2044
|
}
|
|
@@ -2092,7 +2106,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2092
2106
|
state["__dict__"].pop(inherited_var_name, None)
|
|
2093
2107
|
return state
|
|
2094
2108
|
|
|
2095
|
-
def
|
|
2109
|
+
def _check_state_size(
|
|
2096
2110
|
self,
|
|
2097
2111
|
pickle_state_size: int,
|
|
2098
2112
|
):
|
|
@@ -2100,6 +2114,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2100
2114
|
|
|
2101
2115
|
Args:
|
|
2102
2116
|
pickle_state_size: The size of the pickled state.
|
|
2117
|
+
|
|
2118
|
+
Raises:
|
|
2119
|
+
StateTooLargeError: If the state is too large.
|
|
2103
2120
|
"""
|
|
2104
2121
|
state_full_name = self.get_full_name()
|
|
2105
2122
|
if (
|
|
@@ -2107,10 +2124,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2107
2124
|
and pickle_state_size > TOO_LARGE_SERIALIZED_STATE
|
|
2108
2125
|
and self.substates
|
|
2109
2126
|
):
|
|
2110
|
-
|
|
2127
|
+
msg = (
|
|
2111
2128
|
f"State {state_full_name} serializes to {pickle_state_size} bytes "
|
|
2112
|
-
"which may present performance issues. Consider reducing the size of this state."
|
|
2129
|
+
+ "which may present performance issues. Consider reducing the size of this state."
|
|
2113
2130
|
)
|
|
2131
|
+
if environment.REFLEX_PERF_MODE.get() == PerformanceMode.WARN:
|
|
2132
|
+
console.warn(msg)
|
|
2133
|
+
elif environment.REFLEX_PERF_MODE.get() == PerformanceMode.RAISE:
|
|
2134
|
+
raise StateTooLargeError(msg)
|
|
2114
2135
|
_WARNED_ABOUT_STATE_SIZE.add(state_full_name)
|
|
2115
2136
|
|
|
2116
2137
|
@classmethod
|
|
@@ -2152,7 +2173,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2152
2173
|
"""
|
|
2153
2174
|
try:
|
|
2154
2175
|
pickle_state = pickle.dumps((self._to_schema(), self))
|
|
2155
|
-
|
|
2176
|
+
if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
|
|
2177
|
+
self._check_state_size(len(pickle_state))
|
|
2156
2178
|
return pickle_state
|
|
2157
2179
|
except HANDLED_PICKLE_ERRORS as og_pickle_error:
|
|
2158
2180
|
error = (
|
|
@@ -2367,6 +2389,23 @@ class ComponentState(State, mixin=True):
|
|
|
2367
2389
|
# The number of components created from this class.
|
|
2368
2390
|
_per_component_state_instance_count: ClassVar[int] = 0
|
|
2369
2391
|
|
|
2392
|
+
def __init__(self, *args, **kwargs):
|
|
2393
|
+
"""Do not allow direct initialization of the ComponentState.
|
|
2394
|
+
|
|
2395
|
+
Args:
|
|
2396
|
+
*args: The args to pass to the State init method.
|
|
2397
|
+
**kwargs: The kwargs to pass to the State init method.
|
|
2398
|
+
|
|
2399
|
+
Raises:
|
|
2400
|
+
ReflexRuntimeError: If the ComponentState is initialized directly.
|
|
2401
|
+
"""
|
|
2402
|
+
if type(self)._mixin:
|
|
2403
|
+
raise ReflexRuntimeError(
|
|
2404
|
+
f"{ComponentState.__name__} {type(self).__name__} is not meant to be initialized directly. "
|
|
2405
|
+
+ "Use the `create` method to create a new instance and access the state via the `State` attribute."
|
|
2406
|
+
)
|
|
2407
|
+
super().__init__(*args, **kwargs)
|
|
2408
|
+
|
|
2370
2409
|
@classmethod
|
|
2371
2410
|
def __init_subclass__(cls, mixin: bool = True, **kwargs):
|
|
2372
2411
|
"""Overwrite mixin default to True.
|
|
@@ -3526,7 +3565,16 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3526
3565
|
pydantic.BaseModel.__dict__
|
|
3527
3566
|
)
|
|
3528
3567
|
|
|
3529
|
-
|
|
3568
|
+
# These types will be wrapped in MutableProxy
|
|
3569
|
+
__mutable_types__ = (
|
|
3570
|
+
list,
|
|
3571
|
+
dict,
|
|
3572
|
+
set,
|
|
3573
|
+
Base,
|
|
3574
|
+
DeclarativeBase,
|
|
3575
|
+
BaseModelV2,
|
|
3576
|
+
BaseModelV1,
|
|
3577
|
+
)
|
|
3530
3578
|
|
|
3531
3579
|
def __init__(self, wrapped: Any, state: BaseState, field_name: str):
|
|
3532
3580
|
"""Create a proxy for a mutable object that tracks changes.
|
|
@@ -3566,6 +3614,18 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3566
3614
|
if wrapped is not None:
|
|
3567
3615
|
return wrapped(*args, **(kwargs or {}))
|
|
3568
3616
|
|
|
3617
|
+
@classmethod
|
|
3618
|
+
def _is_mutable_type(cls, value: Any) -> bool:
|
|
3619
|
+
"""Check if a value is of a mutable type and should be wrapped.
|
|
3620
|
+
|
|
3621
|
+
Args:
|
|
3622
|
+
value: The value to check.
|
|
3623
|
+
|
|
3624
|
+
Returns:
|
|
3625
|
+
Whether the value is of a mutable type.
|
|
3626
|
+
"""
|
|
3627
|
+
return isinstance(value, cls.__mutable_types__)
|
|
3628
|
+
|
|
3569
3629
|
def _wrap_recursive(self, value: Any) -> Any:
|
|
3570
3630
|
"""Wrap a value recursively if it is mutable.
|
|
3571
3631
|
|
|
@@ -3576,9 +3636,7 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3576
3636
|
The wrapped value.
|
|
3577
3637
|
"""
|
|
3578
3638
|
# Recursively wrap mutable types, but do not re-wrap MutableProxy instances.
|
|
3579
|
-
if
|
|
3580
|
-
value, MutableProxy
|
|
3581
|
-
):
|
|
3639
|
+
if self._is_mutable_type(value) and not isinstance(value, MutableProxy):
|
|
3582
3640
|
return type(self)(
|
|
3583
3641
|
wrapped=value,
|
|
3584
3642
|
state=self._self_state,
|
|
@@ -3636,7 +3694,7 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3636
3694
|
self._wrap_recursive_decorator,
|
|
3637
3695
|
)
|
|
3638
3696
|
|
|
3639
|
-
if
|
|
3697
|
+
if self._is_mutable_type(value) and __name not in (
|
|
3640
3698
|
"__wrapped__",
|
|
3641
3699
|
"_self_state",
|
|
3642
3700
|
):
|
reflex/utils/exceptions.py
CHANGED
|
@@ -151,6 +151,10 @@ class InvalidPropValueError(ReflexError):
|
|
|
151
151
|
"""Raised when a prop value is invalid."""
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
class StateTooLargeError(ReflexError):
|
|
155
|
+
"""Raised when the state is too large to be serialized."""
|
|
156
|
+
|
|
157
|
+
|
|
154
158
|
class SystemPackageMissingError(ReflexError):
|
|
155
159
|
"""Raised when a system package is missing."""
|
|
156
160
|
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -34,7 +34,7 @@ from redis.asyncio import Redis
|
|
|
34
34
|
from reflex import constants, model
|
|
35
35
|
from reflex.compiler import templates
|
|
36
36
|
from reflex.config import Config, environment, get_config
|
|
37
|
-
from reflex.utils import console, net, path_ops, processes
|
|
37
|
+
from reflex.utils import console, net, path_ops, processes, redir
|
|
38
38
|
from reflex.utils.exceptions import (
|
|
39
39
|
GeneratedCodeHasNoFunctionDefs,
|
|
40
40
|
raise_system_package_missing_error,
|
|
@@ -93,6 +93,8 @@ def check_latest_package_version(package_name: str):
|
|
|
93
93
|
Args:
|
|
94
94
|
package_name: The name of the package.
|
|
95
95
|
"""
|
|
96
|
+
if environment.REFLEX_CHECK_LATEST_VERSION.get() is False:
|
|
97
|
+
return
|
|
96
98
|
try:
|
|
97
99
|
# Get the latest version from PyPI
|
|
98
100
|
current_version = importlib.metadata.version(package_name)
|
|
@@ -1209,7 +1211,7 @@ def check_schema_up_to_date():
|
|
|
1209
1211
|
)
|
|
1210
1212
|
|
|
1211
1213
|
|
|
1212
|
-
def
|
|
1214
|
+
def prompt_for_template_options(templates: list[Template]) -> str:
|
|
1213
1215
|
"""Prompt the user to specify a template.
|
|
1214
1216
|
|
|
1215
1217
|
Args:
|
|
@@ -1221,9 +1223,14 @@ def prompt_for_template(templates: list[Template]) -> str:
|
|
|
1221
1223
|
# Show the user the URLs of each template to preview.
|
|
1222
1224
|
console.print("\nGet started with a template:")
|
|
1223
1225
|
|
|
1226
|
+
def format_demo_url_str(url: str) -> str:
|
|
1227
|
+
return f" ({url})" if url else ""
|
|
1228
|
+
|
|
1224
1229
|
# Prompt the user to select a template.
|
|
1225
1230
|
id_to_name = {
|
|
1226
|
-
str(
|
|
1231
|
+
str(
|
|
1232
|
+
idx
|
|
1233
|
+
): f"{template.name.replace('_', ' ').replace('-', ' ')}{format_demo_url_str(template.demo_url)} - {template.description}"
|
|
1227
1234
|
for idx, template in enumerate(templates)
|
|
1228
1235
|
}
|
|
1229
1236
|
for id in range(len(id_to_name)):
|
|
@@ -1378,15 +1385,122 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
|
|
|
1378
1385
|
shutil.rmtree(unzip_dir)
|
|
1379
1386
|
|
|
1380
1387
|
|
|
1381
|
-
def
|
|
1388
|
+
def initialize_default_app(app_name: str):
|
|
1389
|
+
"""Initialize the default app.
|
|
1390
|
+
|
|
1391
|
+
Args:
|
|
1392
|
+
app_name: The name of the app.
|
|
1393
|
+
"""
|
|
1394
|
+
create_config(app_name)
|
|
1395
|
+
initialize_app_directory(app_name)
|
|
1396
|
+
|
|
1397
|
+
|
|
1398
|
+
def validate_and_create_app_using_remote_template(app_name, template, templates):
|
|
1399
|
+
"""Validate and create an app using a remote template.
|
|
1400
|
+
|
|
1401
|
+
Args:
|
|
1402
|
+
app_name: The name of the app.
|
|
1403
|
+
template: The name of the template.
|
|
1404
|
+
templates: The available templates.
|
|
1405
|
+
|
|
1406
|
+
Raises:
|
|
1407
|
+
Exit: If the template is not found.
|
|
1408
|
+
"""
|
|
1409
|
+
# If user selects a template, it needs to exist.
|
|
1410
|
+
if template in templates:
|
|
1411
|
+
template_url = templates[template].code_url
|
|
1412
|
+
else:
|
|
1413
|
+
# Check if the template is a github repo.
|
|
1414
|
+
if template.startswith("https://github.com"):
|
|
1415
|
+
template_url = f"{template.strip('/').replace('.git', '')}/archive/main.zip"
|
|
1416
|
+
else:
|
|
1417
|
+
console.error(f"Template `{template}` not found.")
|
|
1418
|
+
raise typer.Exit(1)
|
|
1419
|
+
|
|
1420
|
+
if template_url is None:
|
|
1421
|
+
return
|
|
1422
|
+
|
|
1423
|
+
create_config_init_app_from_remote_template(
|
|
1424
|
+
app_name=app_name, template_url=template_url
|
|
1425
|
+
)
|
|
1426
|
+
|
|
1427
|
+
|
|
1428
|
+
def generate_template_using_ai(template: str | None = None) -> str:
|
|
1429
|
+
"""Generate a template using AI(Flexgen).
|
|
1430
|
+
|
|
1431
|
+
Args:
|
|
1432
|
+
template: The name of the template.
|
|
1433
|
+
|
|
1434
|
+
Returns:
|
|
1435
|
+
The generation hash.
|
|
1436
|
+
|
|
1437
|
+
Raises:
|
|
1438
|
+
Exit: If the template and ai flags are used.
|
|
1439
|
+
"""
|
|
1440
|
+
if template is None:
|
|
1441
|
+
# If AI is requested and no template specified, redirect the user to reflex.build.
|
|
1442
|
+
return redir.reflex_build_redirect()
|
|
1443
|
+
elif is_generation_hash(template):
|
|
1444
|
+
# Otherwise treat the template as a generation hash.
|
|
1445
|
+
return template
|
|
1446
|
+
else:
|
|
1447
|
+
console.error(
|
|
1448
|
+
"Cannot use `--template` option with `--ai` option. Please remove `--template` option."
|
|
1449
|
+
)
|
|
1450
|
+
raise typer.Exit(2)
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
def fetch_remote_templates(
|
|
1454
|
+
template: Optional[str] = None,
|
|
1455
|
+
) -> tuple[str, dict[str, Template]]:
|
|
1456
|
+
"""Fetch the available remote templates.
|
|
1457
|
+
|
|
1458
|
+
Args:
|
|
1459
|
+
template: The name of the template.
|
|
1460
|
+
|
|
1461
|
+
Returns:
|
|
1462
|
+
The selected template and the available templates.
|
|
1463
|
+
|
|
1464
|
+
Raises:
|
|
1465
|
+
Exit: If the template is not valid or if the template is not specified.
|
|
1466
|
+
"""
|
|
1467
|
+
available_templates = {}
|
|
1468
|
+
|
|
1469
|
+
try:
|
|
1470
|
+
# Get the available templates
|
|
1471
|
+
available_templates = fetch_app_templates(constants.Reflex.VERSION)
|
|
1472
|
+
except Exception as e:
|
|
1473
|
+
console.warn("Failed to fetch templates. Falling back to default template.")
|
|
1474
|
+
console.debug(f"Error while fetching templates: {e}")
|
|
1475
|
+
template = constants.Templates.DEFAULT
|
|
1476
|
+
|
|
1477
|
+
if template == constants.Templates.DEFAULT:
|
|
1478
|
+
return template, available_templates
|
|
1479
|
+
|
|
1480
|
+
if template in available_templates:
|
|
1481
|
+
return template, available_templates
|
|
1482
|
+
|
|
1483
|
+
else:
|
|
1484
|
+
if template is not None:
|
|
1485
|
+
console.error(f"{template!r} is not a valid template name.")
|
|
1486
|
+
console.print(
|
|
1487
|
+
f"Go to the templates page ({constants.Templates.REFLEX_TEMPLATES_URL}) and copy the command to init with a template."
|
|
1488
|
+
)
|
|
1489
|
+
raise typer.Exit(0)
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
def initialize_app(
|
|
1493
|
+
app_name: str, template: str | None = None, ai: bool = False
|
|
1494
|
+
) -> str | None:
|
|
1382
1495
|
"""Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit.
|
|
1383
1496
|
|
|
1384
1497
|
Args:
|
|
1385
1498
|
app_name: The name of the app.
|
|
1386
1499
|
template: The name of the template to use.
|
|
1500
|
+
ai: Whether to use AI to generate the template.
|
|
1387
1501
|
|
|
1388
|
-
|
|
1389
|
-
|
|
1502
|
+
Returns:
|
|
1503
|
+
The name of the template.
|
|
1390
1504
|
"""
|
|
1391
1505
|
# Local imports to avoid circular imports.
|
|
1392
1506
|
from reflex.utils import telemetry
|
|
@@ -1396,51 +1510,71 @@ def initialize_app(app_name: str, template: str | None = None):
|
|
|
1396
1510
|
telemetry.send("reinit")
|
|
1397
1511
|
return
|
|
1398
1512
|
|
|
1513
|
+
generation_hash = None
|
|
1514
|
+
if ai:
|
|
1515
|
+
generation_hash = generate_template_using_ai(template)
|
|
1516
|
+
template = constants.Templates.DEFAULT
|
|
1517
|
+
|
|
1399
1518
|
templates: dict[str, Template] = {}
|
|
1400
1519
|
|
|
1401
1520
|
# Don't fetch app templates if the user directly asked for DEFAULT.
|
|
1402
|
-
if template is None
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
template =
|
|
1521
|
+
if template is not None and (template not in (constants.Templates.DEFAULT,)):
|
|
1522
|
+
template, templates = fetch_remote_templates(template)
|
|
1523
|
+
|
|
1524
|
+
if template is None:
|
|
1525
|
+
template = prompt_for_template_options(get_init_cli_prompt_options())
|
|
1526
|
+
if template == constants.Templates.AI:
|
|
1527
|
+
generation_hash = generate_template_using_ai()
|
|
1528
|
+
# change to the default to allow creation of default app
|
|
1529
|
+
template = constants.Templates.DEFAULT
|
|
1530
|
+
elif template == constants.Templates.CHOOSE_TEMPLATES:
|
|
1531
|
+
template, templates = fetch_remote_templates()
|
|
1413
1532
|
|
|
1414
1533
|
# If the blank template is selected, create a blank app.
|
|
1415
|
-
if template
|
|
1534
|
+
if template in (constants.Templates.DEFAULT,):
|
|
1416
1535
|
# Default app creation behavior: a blank app.
|
|
1417
|
-
|
|
1418
|
-
initialize_app_directory(app_name)
|
|
1536
|
+
initialize_default_app(app_name)
|
|
1419
1537
|
else:
|
|
1420
|
-
|
|
1421
|
-
|
|
1538
|
+
validate_and_create_app_using_remote_template(
|
|
1539
|
+
app_name=app_name, template=template, templates=templates
|
|
1540
|
+
)
|
|
1422
1541
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
template_url = (
|
|
1430
|
-
f"{template.strip('/').replace('.git', '')}/archive/main.zip"
|
|
1431
|
-
)
|
|
1432
|
-
else:
|
|
1433
|
-
console.error(f"Template `{template}` not found.")
|
|
1434
|
-
raise typer.Exit(1)
|
|
1542
|
+
# If a reflex.build generation hash is available, download the code and apply it to the main module.
|
|
1543
|
+
if generation_hash:
|
|
1544
|
+
initialize_main_module_index_from_generation(
|
|
1545
|
+
app_name, generation_hash=generation_hash
|
|
1546
|
+
)
|
|
1547
|
+
telemetry.send("init", template=template)
|
|
1435
1548
|
|
|
1436
|
-
|
|
1437
|
-
return
|
|
1549
|
+
return template
|
|
1438
1550
|
|
|
1439
|
-
create_config_init_app_from_remote_template(
|
|
1440
|
-
app_name=app_name, template_url=template_url
|
|
1441
|
-
)
|
|
1442
1551
|
|
|
1443
|
-
|
|
1552
|
+
def get_init_cli_prompt_options() -> list[Template]:
|
|
1553
|
+
"""Get the CLI options for initializing a Reflex app.
|
|
1554
|
+
|
|
1555
|
+
Returns:
|
|
1556
|
+
The CLI options.
|
|
1557
|
+
"""
|
|
1558
|
+
return [
|
|
1559
|
+
Template(
|
|
1560
|
+
name=constants.Templates.DEFAULT,
|
|
1561
|
+
description="A blank Reflex app.",
|
|
1562
|
+
demo_url=constants.Templates.DEFAULT_TEMPLATE_URL,
|
|
1563
|
+
code_url="",
|
|
1564
|
+
),
|
|
1565
|
+
Template(
|
|
1566
|
+
name=constants.Templates.AI,
|
|
1567
|
+
description="Generate a template using AI [Experimental]",
|
|
1568
|
+
demo_url="",
|
|
1569
|
+
code_url="",
|
|
1570
|
+
),
|
|
1571
|
+
Template(
|
|
1572
|
+
name=constants.Templates.CHOOSE_TEMPLATES,
|
|
1573
|
+
description="Choose an existing template.",
|
|
1574
|
+
demo_url="",
|
|
1575
|
+
code_url="",
|
|
1576
|
+
),
|
|
1577
|
+
]
|
|
1444
1578
|
|
|
1445
1579
|
|
|
1446
1580
|
def initialize_main_module_index_from_generation(app_name: str, generation_hash: str):
|