reflex 0.6.5a3__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 +90 -25
- 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.6.dist-info}/METADATA +3 -2
- {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/RECORD +52 -51
- {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/LICENSE +0 -0
- {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/WHEEL +0 -0
- {reflex-0.6.5a3.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,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)
|
|
@@ -1734,7 +1748,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1734
1748
|
if value is None:
|
|
1735
1749
|
continue
|
|
1736
1750
|
hinted_args = value_inside_optional(hinted_args)
|
|
1737
|
-
if
|
|
1751
|
+
if (
|
|
1752
|
+
isinstance(value, dict)
|
|
1753
|
+
and inspect.isclass(hinted_args)
|
|
1754
|
+
and not types.is_generic_alias(hinted_args) # py3.9-py3.10
|
|
1755
|
+
):
|
|
1738
1756
|
if issubclass(hinted_args, Model):
|
|
1739
1757
|
# Remove non-fields from the payload
|
|
1740
1758
|
payload[arg] = hinted_args(
|
|
@@ -1745,7 +1763,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1745
1763
|
}
|
|
1746
1764
|
)
|
|
1747
1765
|
elif dataclasses.is_dataclass(hinted_args) or issubclass(
|
|
1748
|
-
hinted_args, Base
|
|
1766
|
+
hinted_args, (Base, BaseModelV1, BaseModelV2)
|
|
1749
1767
|
):
|
|
1750
1768
|
payload[arg] = hinted_args(**value)
|
|
1751
1769
|
if isinstance(value, list) and (hinted_args is set or hinted_args is Set):
|
|
@@ -1890,7 +1908,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1890
1908
|
)
|
|
1891
1909
|
|
|
1892
1910
|
subdelta: Dict[str, Any] = {
|
|
1893
|
-
prop: self.get_value(
|
|
1911
|
+
prop: self.get_value(prop)
|
|
1894
1912
|
for prop in delta_vars
|
|
1895
1913
|
if not types.is_backend_base_variable(prop, type(self))
|
|
1896
1914
|
}
|
|
@@ -1941,6 +1959,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1941
1959
|
if var in self.base_vars or var in self._backend_vars:
|
|
1942
1960
|
self._was_touched = True
|
|
1943
1961
|
break
|
|
1962
|
+
if var == constants.ROUTER_DATA and self.parent_state is None:
|
|
1963
|
+
self._was_touched = True
|
|
1964
|
+
break
|
|
1944
1965
|
|
|
1945
1966
|
def _get_was_touched(self) -> bool:
|
|
1946
1967
|
"""Check current dirty_vars and flag to determine if state instance was modified.
|
|
@@ -1982,9 +2003,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1982
2003
|
Returns:
|
|
1983
2004
|
The value of the field.
|
|
1984
2005
|
"""
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2006
|
+
value = super().get_value(key)
|
|
2007
|
+
if isinstance(value, MutableProxy):
|
|
2008
|
+
return value.__wrapped__
|
|
2009
|
+
return value
|
|
1988
2010
|
|
|
1989
2011
|
def dict(
|
|
1990
2012
|
self, include_computed: bool = True, initial: bool = False, **kwargs
|
|
@@ -2006,8 +2028,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2006
2028
|
self._mark_dirty()
|
|
2007
2029
|
|
|
2008
2030
|
base_vars = {
|
|
2009
|
-
prop_name: self.get_value(
|
|
2010
|
-
for prop_name in self.base_vars
|
|
2031
|
+
prop_name: self.get_value(prop_name) for prop_name in self.base_vars
|
|
2011
2032
|
}
|
|
2012
2033
|
if initial and include_computed:
|
|
2013
2034
|
computed_vars = {
|
|
@@ -2016,7 +2037,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2016
2037
|
cv._initial_value
|
|
2017
2038
|
if is_computed_var(cv)
|
|
2018
2039
|
and not isinstance(cv._initial_value, types.Unset)
|
|
2019
|
-
else self.get_value(
|
|
2040
|
+
else self.get_value(prop_name)
|
|
2020
2041
|
)
|
|
2021
2042
|
for prop_name, cv in self.computed_vars.items()
|
|
2022
2043
|
if not cv._backend
|
|
@@ -2024,7 +2045,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2024
2045
|
elif include_computed:
|
|
2025
2046
|
computed_vars = {
|
|
2026
2047
|
# Include the computed vars.
|
|
2027
|
-
prop_name: self.get_value(
|
|
2048
|
+
prop_name: self.get_value(prop_name)
|
|
2028
2049
|
for prop_name, cv in self.computed_vars.items()
|
|
2029
2050
|
if not cv._backend
|
|
2030
2051
|
}
|
|
@@ -2092,7 +2113,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2092
2113
|
state["__dict__"].pop(inherited_var_name, None)
|
|
2093
2114
|
return state
|
|
2094
2115
|
|
|
2095
|
-
def
|
|
2116
|
+
def _check_state_size(
|
|
2096
2117
|
self,
|
|
2097
2118
|
pickle_state_size: int,
|
|
2098
2119
|
):
|
|
@@ -2100,6 +2121,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2100
2121
|
|
|
2101
2122
|
Args:
|
|
2102
2123
|
pickle_state_size: The size of the pickled state.
|
|
2124
|
+
|
|
2125
|
+
Raises:
|
|
2126
|
+
StateTooLargeError: If the state is too large.
|
|
2103
2127
|
"""
|
|
2104
2128
|
state_full_name = self.get_full_name()
|
|
2105
2129
|
if (
|
|
@@ -2107,10 +2131,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2107
2131
|
and pickle_state_size > TOO_LARGE_SERIALIZED_STATE
|
|
2108
2132
|
and self.substates
|
|
2109
2133
|
):
|
|
2110
|
-
|
|
2134
|
+
msg = (
|
|
2111
2135
|
f"State {state_full_name} serializes to {pickle_state_size} bytes "
|
|
2112
|
-
"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."
|
|
2113
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)
|
|
2114
2142
|
_WARNED_ABOUT_STATE_SIZE.add(state_full_name)
|
|
2115
2143
|
|
|
2116
2144
|
@classmethod
|
|
@@ -2152,7 +2180,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2152
2180
|
"""
|
|
2153
2181
|
try:
|
|
2154
2182
|
pickle_state = pickle.dumps((self._to_schema(), self))
|
|
2155
|
-
|
|
2183
|
+
if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
|
|
2184
|
+
self._check_state_size(len(pickle_state))
|
|
2156
2185
|
return pickle_state
|
|
2157
2186
|
except HANDLED_PICKLE_ERRORS as og_pickle_error:
|
|
2158
2187
|
error = (
|
|
@@ -2367,6 +2396,23 @@ class ComponentState(State, mixin=True):
|
|
|
2367
2396
|
# The number of components created from this class.
|
|
2368
2397
|
_per_component_state_instance_count: ClassVar[int] = 0
|
|
2369
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
|
+
|
|
2370
2416
|
@classmethod
|
|
2371
2417
|
def __init_subclass__(cls, mixin: bool = True, **kwargs):
|
|
2372
2418
|
"""Overwrite mixin default to True.
|
|
@@ -3526,7 +3572,16 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3526
3572
|
pydantic.BaseModel.__dict__
|
|
3527
3573
|
)
|
|
3528
3574
|
|
|
3529
|
-
|
|
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
|
+
)
|
|
3530
3585
|
|
|
3531
3586
|
def __init__(self, wrapped: Any, state: BaseState, field_name: str):
|
|
3532
3587
|
"""Create a proxy for a mutable object that tracks changes.
|
|
@@ -3566,6 +3621,18 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3566
3621
|
if wrapped is not None:
|
|
3567
3622
|
return wrapped(*args, **(kwargs or {}))
|
|
3568
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
|
+
|
|
3569
3636
|
def _wrap_recursive(self, value: Any) -> Any:
|
|
3570
3637
|
"""Wrap a value recursively if it is mutable.
|
|
3571
3638
|
|
|
@@ -3576,9 +3643,7 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3576
3643
|
The wrapped value.
|
|
3577
3644
|
"""
|
|
3578
3645
|
# Recursively wrap mutable types, but do not re-wrap MutableProxy instances.
|
|
3579
|
-
if
|
|
3580
|
-
value, MutableProxy
|
|
3581
|
-
):
|
|
3646
|
+
if self._is_mutable_type(value) and not isinstance(value, MutableProxy):
|
|
3582
3647
|
return type(self)(
|
|
3583
3648
|
wrapped=value,
|
|
3584
3649
|
state=self._self_state,
|
|
@@ -3636,7 +3701,7 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
3636
3701
|
self._wrap_recursive_decorator,
|
|
3637
3702
|
)
|
|
3638
3703
|
|
|
3639
|
-
if
|
|
3704
|
+
if self._is_mutable_type(value) and __name not in (
|
|
3640
3705
|
"__wrapped__",
|
|
3641
3706
|
"_self_state",
|
|
3642
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
|
|