reflex 0.6.5a2__py3-none-any.whl → 0.6.6__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 +39 -14
- reflex/__init__.py +3 -1
- reflex/__init__.pyi +3 -0
- reflex/app.py +24 -4
- 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/core/upload.py +8 -5
- 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/plotly/plotly.py +1 -1
- 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/recharts/recharts.py +2 -14
- reflex/components/recharts/recharts.pyi +0 -1
- 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 +16 -35
- reflex/state.py +99 -28
- 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.5a2.dist-info → reflex-0.6.6.dist-info}/METADATA +3 -2
- {reflex-0.6.5a2.dist-info → reflex-0.6.6.dist-info}/RECORD +52 -51
- {reflex-0.6.5a2.dist-info → reflex-0.6.6.dist-info}/LICENSE +0 -0
- {reflex-0.6.5a2.dist-info → reflex-0.6.6.dist-info}/WHEEL +0 -0
- {reflex-0.6.5a2.dist-info → reflex-0.6.6.dist-info}/entry_points.txt +0 -0
reflex/event.py
CHANGED
|
@@ -45,6 +45,8 @@ from reflex.vars import VarData
|
|
|
45
45
|
from reflex.vars.base import LiteralVar, Var
|
|
46
46
|
from reflex.vars.function import (
|
|
47
47
|
ArgsFunctionOperation,
|
|
48
|
+
ArgsFunctionOperationBuilder,
|
|
49
|
+
BuilderFunctionVar,
|
|
48
50
|
FunctionArgs,
|
|
49
51
|
FunctionStringVar,
|
|
50
52
|
FunctionVar,
|
|
@@ -179,6 +181,18 @@ class EventActionsMixin:
|
|
|
179
181
|
event_actions={"debounce": delay_ms, **self.event_actions},
|
|
180
182
|
)
|
|
181
183
|
|
|
184
|
+
@property
|
|
185
|
+
def temporal(self):
|
|
186
|
+
"""Do not queue the event if the backend is down.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
New EventHandler-like with temporal set to True.
|
|
190
|
+
"""
|
|
191
|
+
return dataclasses.replace(
|
|
192
|
+
self,
|
|
193
|
+
event_actions={"temporal": True, **self.event_actions},
|
|
194
|
+
)
|
|
195
|
+
|
|
182
196
|
|
|
183
197
|
@dataclasses.dataclass(
|
|
184
198
|
init=True,
|
|
@@ -797,8 +811,7 @@ def scroll_to(elem_id: str, align_to_top: bool | Var[bool] = True) -> EventSpec:
|
|
|
797
811
|
get_element_by_id = FunctionStringVar.create("document.getElementById")
|
|
798
812
|
|
|
799
813
|
return run_script(
|
|
800
|
-
get_element_by_id(elem_id)
|
|
801
|
-
.call(elem_id)
|
|
814
|
+
get_element_by_id.call(elem_id)
|
|
802
815
|
.to(ObjectVar)
|
|
803
816
|
.scrollIntoView.to(FunctionVar)
|
|
804
817
|
.call(align_to_top),
|
|
@@ -899,7 +912,7 @@ def remove_session_storage(key: str) -> EventSpec:
|
|
|
899
912
|
)
|
|
900
913
|
|
|
901
914
|
|
|
902
|
-
def set_clipboard(content: str) -> EventSpec:
|
|
915
|
+
def set_clipboard(content: Union[str, Var[str]]) -> EventSpec:
|
|
903
916
|
"""Set the text in content in the clipboard.
|
|
904
917
|
|
|
905
918
|
Args:
|
|
@@ -1345,6 +1358,10 @@ def check_fn_match_arg_spec(
|
|
|
1345
1358
|
EventFnArgMismatch: Raised if the number of mandatory arguments do not match
|
|
1346
1359
|
"""
|
|
1347
1360
|
user_args = inspect.getfullargspec(user_func).args
|
|
1361
|
+
# Drop the first argument if it's a bound method
|
|
1362
|
+
if inspect.ismethod(user_func) and user_func.__self__ is not None:
|
|
1363
|
+
user_args = user_args[1:]
|
|
1364
|
+
|
|
1348
1365
|
user_default_args = inspect.getfullargspec(user_func).defaults
|
|
1349
1366
|
number_of_user_args = len(user_args) - number_of_bound_args
|
|
1350
1367
|
number_of_user_default_args = len(user_default_args) if user_default_args else 0
|
|
@@ -1580,7 +1597,7 @@ class LiteralEventVar(VarOperationCall, LiteralVar, EventVar):
|
|
|
1580
1597
|
)
|
|
1581
1598
|
|
|
1582
1599
|
|
|
1583
|
-
class EventChainVar(
|
|
1600
|
+
class EventChainVar(BuilderFunctionVar, python_types=EventChain):
|
|
1584
1601
|
"""Base class for event chain vars."""
|
|
1585
1602
|
|
|
1586
1603
|
|
|
@@ -1592,7 +1609,7 @@ class EventChainVar(FunctionVar, python_types=EventChain):
|
|
|
1592
1609
|
# Note: LiteralVar is second in the inheritance list allowing it act like a
|
|
1593
1610
|
# CachedVarOperation (ArgsFunctionOperation) and get the _js_expr from the
|
|
1594
1611
|
# _cached_var_name property.
|
|
1595
|
-
class LiteralEventChainVar(
|
|
1612
|
+
class LiteralEventChainVar(ArgsFunctionOperationBuilder, LiteralVar, EventChainVar):
|
|
1596
1613
|
"""A literal event chain var."""
|
|
1597
1614
|
|
|
1598
1615
|
_var_value: EventChain = dataclasses.field(default=None) # type: ignore
|
reflex/experimental/assets.py
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"""Helper functions for adding assets to the app."""
|
|
2
2
|
|
|
3
|
-
import inspect
|
|
4
|
-
from pathlib import Path
|
|
5
3
|
from typing import Optional
|
|
6
4
|
|
|
7
|
-
from reflex import
|
|
5
|
+
from reflex import assets
|
|
6
|
+
from reflex.utils import console
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
def asset(relative_filename: str, subfolder: Optional[str] = None) -> str:
|
|
11
|
-
"""
|
|
10
|
+
"""DEPRECATED: use `rx.asset` with `shared=True` instead.
|
|
11
|
+
|
|
12
|
+
Add an asset to the app.
|
|
12
13
|
Place the file next to your including python file.
|
|
13
14
|
Copies the file to the app's external assets directory.
|
|
14
15
|
|
|
@@ -22,38 +23,15 @@ def asset(relative_filename: str, subfolder: Optional[str] = None) -> str:
|
|
|
22
23
|
relative_filename: The relative filename of the asset.
|
|
23
24
|
subfolder: The directory to place the asset in.
|
|
24
25
|
|
|
25
|
-
Raises:
|
|
26
|
-
FileNotFoundError: If the file does not exist.
|
|
27
|
-
ValueError: If the module is None.
|
|
28
|
-
|
|
29
26
|
Returns:
|
|
30
27
|
The relative URL to the copied asset.
|
|
31
28
|
"""
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
src_file = Path(calling_file).parent / relative_filename
|
|
42
|
-
|
|
43
|
-
assets = constants.Dirs.APP_ASSETS
|
|
44
|
-
external = constants.Dirs.EXTERNAL_APP_ASSETS
|
|
45
|
-
|
|
46
|
-
if not src_file.exists():
|
|
47
|
-
raise FileNotFoundError(f"File not found: {src_file}")
|
|
48
|
-
|
|
49
|
-
# Create the asset folder in the currently compiling app.
|
|
50
|
-
asset_folder = Path.cwd() / assets / external / subfolder
|
|
51
|
-
asset_folder.mkdir(parents=True, exist_ok=True)
|
|
52
|
-
|
|
53
|
-
dst_file = asset_folder / relative_filename
|
|
54
|
-
|
|
55
|
-
if not dst_file.exists():
|
|
56
|
-
dst_file.symlink_to(src_file)
|
|
57
|
-
|
|
58
|
-
asset_url = f"/{external}/{subfolder}/{relative_filename}"
|
|
59
|
-
return asset_url
|
|
29
|
+
console.deprecate(
|
|
30
|
+
feature_name="rx._x.asset",
|
|
31
|
+
reason="Use `rx.asset` with `shared=True` instead of `rx._x.asset`.",
|
|
32
|
+
deprecation_version="0.6.6",
|
|
33
|
+
removal_version="0.7.0",
|
|
34
|
+
)
|
|
35
|
+
return assets.asset(
|
|
36
|
+
relative_filename, shared=True, subfolder=subfolder, _stack_level=2
|
|
37
|
+
)
|
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,11 +398,13 @@ 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()
|
|
423
406
|
console.debug("Deleting access token from config locally")
|
|
424
|
-
hosting.delete_token_from_config(
|
|
407
|
+
hosting.delete_token_from_config()
|
|
425
408
|
|
|
426
409
|
|
|
427
410
|
db_cli = typer.Typer()
|
|
@@ -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,9 +43,10 @@ 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
|
+
from reflex.model import Model
|
|
49
50
|
from reflex.vars.base import (
|
|
50
51
|
ComputedVar,
|
|
51
52
|
DynamicRouteVar,
|
|
@@ -61,6 +62,13 @@ try:
|
|
|
61
62
|
except ModuleNotFoundError:
|
|
62
63
|
import pydantic
|
|
63
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
|
+
|
|
64
72
|
import wrapt
|
|
65
73
|
from redis.asyncio import Redis
|
|
66
74
|
from redis.exceptions import ResponseError
|
|
@@ -86,8 +94,10 @@ from reflex.utils.exceptions import (
|
|
|
86
94
|
ImmutableStateError,
|
|
87
95
|
InvalidStateManagerMode,
|
|
88
96
|
LockExpiredError,
|
|
97
|
+
ReflexRuntimeError,
|
|
89
98
|
SetUndefinedStateVarError,
|
|
90
99
|
StateSchemaMismatchError,
|
|
100
|
+
StateTooLargeError,
|
|
91
101
|
)
|
|
92
102
|
from reflex.utils.exec import is_testing_env
|
|
93
103
|
from reflex.utils.serializers import serializer
|
|
@@ -108,10 +118,11 @@ Delta = Dict[str, Any]
|
|
|
108
118
|
var = computed_var
|
|
109
119
|
|
|
110
120
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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()
|
|
115
126
|
|
|
116
127
|
# Errors caught during pickling of state
|
|
117
128
|
HANDLED_PICKLE_ERRORS = (
|
|
@@ -386,6 +397,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
386
397
|
"State classes should not be instantiated directly in a Reflex app. "
|
|
387
398
|
"See https://reflex.dev/docs/state/ for further information."
|
|
388
399
|
)
|
|
400
|
+
if type(self)._mixin:
|
|
401
|
+
raise ReflexRuntimeError(
|
|
402
|
+
f"{type(self).__name__} is a state mixin and cannot be instantiated directly."
|
|
403
|
+
)
|
|
389
404
|
kwargs["parent_state"] = parent_state
|
|
390
405
|
super().__init__()
|
|
391
406
|
for name, value in kwargs.items():
|
|
@@ -1242,7 +1257,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1242
1257
|
if parent_state is not None:
|
|
1243
1258
|
return getattr(parent_state, name)
|
|
1244
1259
|
|
|
1245
|
-
if
|
|
1260
|
+
if MutableProxy._is_mutable_type(value) and (
|
|
1246
1261
|
name in super().__getattribute__("base_vars") or name in backend_vars
|
|
1247
1262
|
):
|
|
1248
1263
|
# track changes in mutable containers (list, dict, set, etc)
|
|
@@ -1736,12 +1751,21 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1736
1751
|
if (
|
|
1737
1752
|
isinstance(value, dict)
|
|
1738
1753
|
and inspect.isclass(hinted_args)
|
|
1739
|
-
and (
|
|
1740
|
-
dataclasses.is_dataclass(hinted_args)
|
|
1741
|
-
or issubclass(hinted_args, Base)
|
|
1742
|
-
)
|
|
1754
|
+
and not types.is_generic_alias(hinted_args) # py3.9-py3.10
|
|
1743
1755
|
):
|
|
1744
|
-
|
|
1756
|
+
if issubclass(hinted_args, Model):
|
|
1757
|
+
# Remove non-fields from the payload
|
|
1758
|
+
payload[arg] = hinted_args(
|
|
1759
|
+
**{
|
|
1760
|
+
key: value
|
|
1761
|
+
for key, value in value.items()
|
|
1762
|
+
if key in hinted_args.__fields__
|
|
1763
|
+
}
|
|
1764
|
+
)
|
|
1765
|
+
elif dataclasses.is_dataclass(hinted_args) or issubclass(
|
|
1766
|
+
hinted_args, (Base, BaseModelV1, BaseModelV2)
|
|
1767
|
+
):
|
|
1768
|
+
payload[arg] = hinted_args(**value)
|
|
1745
1769
|
if isinstance(value, list) and (hinted_args is set or hinted_args is Set):
|
|
1746
1770
|
payload[arg] = set(value)
|
|
1747
1771
|
if isinstance(value, list) and (
|
|
@@ -1884,7 +1908,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1884
1908
|
)
|
|
1885
1909
|
|
|
1886
1910
|
subdelta: Dict[str, Any] = {
|
|
1887
|
-
prop: self.get_value(
|
|
1911
|
+
prop: self.get_value(prop)
|
|
1888
1912
|
for prop in delta_vars
|
|
1889
1913
|
if not types.is_backend_base_variable(prop, type(self))
|
|
1890
1914
|
}
|
|
@@ -1935,6 +1959,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1935
1959
|
if var in self.base_vars or var in self._backend_vars:
|
|
1936
1960
|
self._was_touched = True
|
|
1937
1961
|
break
|
|
1962
|
+
if var == constants.ROUTER_DATA and self.parent_state is None:
|
|
1963
|
+
self._was_touched = True
|
|
1964
|
+
break
|
|
1938
1965
|
|
|
1939
1966
|
def _get_was_touched(self) -> bool:
|
|
1940
1967
|
"""Check current dirty_vars and flag to determine if state instance was modified.
|
|
@@ -1976,9 +2003,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1976
2003
|
Returns:
|
|
1977
2004
|
The value of the field.
|
|
1978
2005
|
"""
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
2006
|
+
value = super().get_value(key)
|
|
2007
|
+
if isinstance(value, MutableProxy):
|
|
2008
|
+
return value.__wrapped__
|
|
2009
|
+
return value
|
|
1982
2010
|
|
|
1983
2011
|
def dict(
|
|
1984
2012
|
self, include_computed: bool = True, initial: bool = False, **kwargs
|
|
@@ -2000,8 +2028,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2000
2028
|
self._mark_dirty()
|
|
2001
2029
|
|
|
2002
2030
|
base_vars = {
|
|
2003
|
-
prop_name: self.get_value(
|
|
2004
|
-
for prop_name in self.base_vars
|
|
2031
|
+
prop_name: self.get_value(prop_name) for prop_name in self.base_vars
|
|
2005
2032
|
}
|
|
2006
2033
|
if initial and include_computed:
|
|
2007
2034
|
computed_vars = {
|
|
@@ -2010,7 +2037,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2010
2037
|
cv._initial_value
|
|
2011
2038
|
if is_computed_var(cv)
|
|
2012
2039
|
and not isinstance(cv._initial_value, types.Unset)
|
|
2013
|
-
else self.get_value(
|
|
2040
|
+
else self.get_value(prop_name)
|
|
2014
2041
|
)
|
|
2015
2042
|
for prop_name, cv in self.computed_vars.items()
|
|
2016
2043
|
if not cv._backend
|
|
@@ -2018,7 +2045,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2018
2045
|
elif include_computed:
|
|
2019
2046
|
computed_vars = {
|
|
2020
2047
|
# Include the computed vars.
|
|
2021
|
-
prop_name: self.get_value(
|
|
2048
|
+
prop_name: self.get_value(prop_name)
|
|
2022
2049
|
for prop_name, cv in self.computed_vars.items()
|
|
2023
2050
|
if not cv._backend
|
|
2024
2051
|
}
|
|
@@ -2086,7 +2113,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2086
2113
|
state["__dict__"].pop(inherited_var_name, None)
|
|
2087
2114
|
return state
|
|
2088
2115
|
|
|
2089
|
-
def
|
|
2116
|
+
def _check_state_size(
|
|
2090
2117
|
self,
|
|
2091
2118
|
pickle_state_size: int,
|
|
2092
2119
|
):
|
|
@@ -2094,6 +2121,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2094
2121
|
|
|
2095
2122
|
Args:
|
|
2096
2123
|
pickle_state_size: The size of the pickled state.
|
|
2124
|
+
|
|
2125
|
+
Raises:
|
|
2126
|
+
StateTooLargeError: If the state is too large.
|
|
2097
2127
|
"""
|
|
2098
2128
|
state_full_name = self.get_full_name()
|
|
2099
2129
|
if (
|
|
@@ -2101,10 +2131,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2101
2131
|
and pickle_state_size > TOO_LARGE_SERIALIZED_STATE
|
|
2102
2132
|
and self.substates
|
|
2103
2133
|
):
|
|
2104
|
-
|
|
2134
|
+
msg = (
|
|
2105
2135
|
f"State {state_full_name} serializes to {pickle_state_size} bytes "
|
|
2106
|
-
"which may present performance issues. Consider reducing the size of this state."
|
|
2136
|
+
+ "which may present performance issues. Consider reducing the size of this state."
|
|
2107
2137
|
)
|
|
2138
|
+
if environment.REFLEX_PERF_MODE.get() == PerformanceMode.WARN:
|
|
2139
|
+
console.warn(msg)
|
|
2140
|
+
elif environment.REFLEX_PERF_MODE.get() == PerformanceMode.RAISE:
|
|
2141
|
+
raise StateTooLargeError(msg)
|
|
2108
2142
|
_WARNED_ABOUT_STATE_SIZE.add(state_full_name)
|
|
2109
2143
|
|
|
2110
2144
|
@classmethod
|
|
@@ -2146,7 +2180,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2146
2180
|
"""
|
|
2147
2181
|
try:
|
|
2148
2182
|
pickle_state = pickle.dumps((self._to_schema(), self))
|
|
2149
|
-
|
|
2183
|
+
if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
|
|
2184
|
+
self._check_state_size(len(pickle_state))
|
|
2150
2185
|
return pickle_state
|
|
2151
2186
|
except HANDLED_PICKLE_ERRORS as og_pickle_error:
|
|
2152
2187
|
error = (
|
|
@@ -2361,6 +2396,23 @@ class ComponentState(State, mixin=True):
|
|
|
2361
2396
|
# The number of components created from this class.
|
|
2362
2397
|
_per_component_state_instance_count: ClassVar[int] = 0
|
|
2363
2398
|
|
|
2399
|
+
def __init__(self, *args, **kwargs):
|
|
2400
|
+
"""Do not allow direct initialization of the ComponentState.
|
|
2401
|
+
|
|
2402
|
+
Args:
|
|
2403
|
+
*args: The args to pass to the State init method.
|
|
2404
|
+
**kwargs: The kwargs to pass to the State init method.
|
|
2405
|
+
|
|
2406
|
+
Raises:
|
|
2407
|
+
ReflexRuntimeError: If the ComponentState is initialized directly.
|
|
2408
|
+
"""
|
|
2409
|
+
if type(self)._mixin:
|
|
2410
|
+
raise ReflexRuntimeError(
|
|
2411
|
+
f"{ComponentState.__name__} {type(self).__name__} is not meant to be initialized directly. "
|
|
2412
|
+
+ "Use the `create` method to create a new instance and access the state via the `State` attribute."
|
|
2413
|
+
)
|
|
2414
|
+
super().__init__(*args, **kwargs)
|
|
2415
|
+
|
|
2364
2416
|
@classmethod
|
|
2365
2417
|
def __init_subclass__(cls, mixin: bool = True, **kwargs):
|
|
2366
2418
|
"""Overwrite mixin default to True.
|
|
@@ -3520,7 +3572,16 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3520
3572
|
pydantic.BaseModel.__dict__
|
|
3521
3573
|
)
|
|
3522
3574
|
|
|
3523
|
-
|
|
3575
|
+
# These types will be wrapped in MutableProxy
|
|
3576
|
+
__mutable_types__ = (
|
|
3577
|
+
list,
|
|
3578
|
+
dict,
|
|
3579
|
+
set,
|
|
3580
|
+
Base,
|
|
3581
|
+
DeclarativeBase,
|
|
3582
|
+
BaseModelV2,
|
|
3583
|
+
BaseModelV1,
|
|
3584
|
+
)
|
|
3524
3585
|
|
|
3525
3586
|
def __init__(self, wrapped: Any, state: BaseState, field_name: str):
|
|
3526
3587
|
"""Create a proxy for a mutable object that tracks changes.
|
|
@@ -3560,6 +3621,18 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3560
3621
|
if wrapped is not None:
|
|
3561
3622
|
return wrapped(*args, **(kwargs or {}))
|
|
3562
3623
|
|
|
3624
|
+
@classmethod
|
|
3625
|
+
def _is_mutable_type(cls, value: Any) -> bool:
|
|
3626
|
+
"""Check if a value is of a mutable type and should be wrapped.
|
|
3627
|
+
|
|
3628
|
+
Args:
|
|
3629
|
+
value: The value to check.
|
|
3630
|
+
|
|
3631
|
+
Returns:
|
|
3632
|
+
Whether the value is of a mutable type.
|
|
3633
|
+
"""
|
|
3634
|
+
return isinstance(value, cls.__mutable_types__)
|
|
3635
|
+
|
|
3563
3636
|
def _wrap_recursive(self, value: Any) -> Any:
|
|
3564
3637
|
"""Wrap a value recursively if it is mutable.
|
|
3565
3638
|
|
|
@@ -3570,9 +3643,7 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3570
3643
|
The wrapped value.
|
|
3571
3644
|
"""
|
|
3572
3645
|
# Recursively wrap mutable types, but do not re-wrap MutableProxy instances.
|
|
3573
|
-
if
|
|
3574
|
-
value, MutableProxy
|
|
3575
|
-
):
|
|
3646
|
+
if self._is_mutable_type(value) and not isinstance(value, MutableProxy):
|
|
3576
3647
|
return type(self)(
|
|
3577
3648
|
wrapped=value,
|
|
3578
3649
|
state=self._self_state,
|
|
@@ -3630,7 +3701,7 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3630
3701
|
self._wrap_recursive_decorator,
|
|
3631
3702
|
)
|
|
3632
3703
|
|
|
3633
|
-
if
|
|
3704
|
+
if self._is_mutable_type(value) and __name not in (
|
|
3634
3705
|
"__wrapped__",
|
|
3635
3706
|
"_self_state",
|
|
3636
3707
|
):
|
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
|
|