reflex 0.5.10a3__py3-none-any.whl → 0.6.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of reflex might be problematic. Click here for more details.
- reflex/.templates/jinja/web/pages/utils.js.jinja2 +4 -4
- reflex/.templates/jinja/web/utils/context.js.jinja2 +1 -1
- reflex/.templates/jinja/web/utils/theme.js.jinja2 +1 -1
- reflex/__init__.py +3 -2
- reflex/__init__.pyi +2 -2
- reflex/app.py +43 -9
- reflex/base.py +3 -2
- reflex/compiler/compiler.py +6 -6
- reflex/compiler/utils.py +5 -3
- reflex/components/base/app_wrap.py +2 -4
- reflex/components/base/app_wrap.pyi +17 -17
- reflex/components/base/bare.py +7 -4
- reflex/components/base/body.pyi +17 -17
- reflex/components/base/document.pyi +81 -81
- reflex/components/base/error_boundary.py +10 -8
- reflex/components/base/error_boundary.pyi +20 -19
- reflex/components/base/fragment.pyi +17 -17
- reflex/components/base/head.pyi +33 -33
- reflex/components/base/link.pyi +34 -33
- reflex/components/base/meta.pyi +65 -65
- reflex/components/base/script.py +2 -1
- reflex/components/base/script.pyi +21 -20
- reflex/components/component.py +116 -145
- reflex/components/core/banner.py +59 -60
- reflex/components/core/banner.pyi +86 -150
- reflex/components/core/client_side_routing.py +2 -1
- reflex/components/core/client_side_routing.pyi +34 -33
- reflex/components/core/clipboard.py +2 -2
- reflex/components/core/clipboard.pyi +19 -18
- reflex/components/core/cond.py +21 -44
- reflex/components/core/debounce.py +6 -8
- reflex/components/core/debounce.pyi +19 -18
- reflex/components/core/foreach.py +5 -14
- reflex/components/core/html.pyi +18 -17
- reflex/components/core/match.py +36 -43
- reflex/components/core/upload.py +32 -25
- reflex/components/core/upload.pyi +84 -73
- reflex/components/datadisplay/code.py +55 -28
- reflex/components/datadisplay/code.pyi +20 -17
- reflex/components/datadisplay/dataeditor.py +17 -11
- reflex/components/datadisplay/dataeditor.pyi +34 -33
- reflex/components/el/__init__.py +0 -1
- reflex/components/el/__init__.pyi +0 -11
- reflex/components/el/element.pyi +17 -17
- reflex/components/el/elements/__init__.py +1 -7
- reflex/components/el/elements/__init__.pyi +1 -15
- reflex/components/el/elements/base.pyi +18 -17
- reflex/components/el/elements/forms.py +24 -31
- reflex/components/el/elements/forms.pyi +237 -236
- reflex/components/el/elements/inline.pyi +450 -449
- reflex/components/el/elements/media.py +0 -21
- reflex/components/el/elements/media.pyi +338 -337
- reflex/components/el/elements/metadata.py +3 -2
- reflex/components/el/elements/metadata.pyi +98 -97
- reflex/components/el/elements/other.pyi +114 -113
- reflex/components/el/elements/scripts.pyi +50 -49
- reflex/components/el/elements/sectioning.pyi +242 -241
- reflex/components/el/elements/tables.pyi +162 -161
- reflex/components/el/elements/typography.pyi +242 -241
- reflex/components/gridjs/datatable.py +13 -14
- reflex/components/gridjs/datatable.pyi +34 -33
- reflex/components/lucide/icon.py +2 -126
- reflex/components/lucide/icon.pyi +34 -142
- reflex/components/markdown/markdown.py +30 -35
- reflex/components/markdown/markdown.pyi +29 -32
- reflex/components/moment/moment.pyi +19 -18
- reflex/components/next/base.pyi +17 -17
- reflex/components/next/image.py +0 -4
- reflex/components/next/image.pyi +20 -19
- reflex/components/next/link.pyi +18 -17
- reflex/components/next/video.pyi +18 -17
- reflex/components/plotly/plotly.py +16 -28
- reflex/components/plotly/plotly.pyi +36 -35
- reflex/components/props.py +21 -10
- reflex/components/radix/__init__.pyi +1 -1
- reflex/components/radix/primitives/__init__.pyi +0 -1
- reflex/components/radix/primitives/accordion.py +7 -8
- reflex/components/radix/primitives/accordion.pyi +117 -116
- reflex/components/radix/primitives/base.pyi +34 -33
- reflex/components/radix/primitives/drawer.pyi +169 -168
- reflex/components/radix/primitives/form.pyi +168 -167
- reflex/components/radix/primitives/progress.pyi +82 -81
- reflex/components/radix/primitives/slider.pyi +84 -83
- reflex/components/radix/themes/base.py +8 -4
- reflex/components/radix/themes/base.pyi +114 -113
- reflex/components/radix/themes/color_mode.py +12 -21
- reflex/components/radix/themes/color_mode.pyi +67 -67
- reflex/components/radix/themes/components/__init__.pyi +1 -0
- reflex/components/radix/themes/components/alert_dialog.pyi +118 -117
- reflex/components/radix/themes/components/aspect_ratio.pyi +18 -17
- reflex/components/radix/themes/components/avatar.pyi +18 -17
- reflex/components/radix/themes/components/badge.pyi +18 -17
- reflex/components/radix/themes/components/button.pyi +18 -17
- reflex/components/radix/themes/components/callout.pyi +82 -81
- reflex/components/radix/themes/components/card.pyi +18 -17
- reflex/components/radix/themes/components/checkbox.py +2 -3
- reflex/components/radix/themes/components/checkbox.pyi +53 -52
- reflex/components/radix/themes/components/checkbox_cards.pyi +34 -33
- reflex/components/radix/themes/components/checkbox_group.pyi +34 -33
- reflex/components/radix/themes/components/context_menu.pyi +140 -139
- reflex/components/radix/themes/components/data_list.py +5 -0
- reflex/components/radix/themes/components/data_list.pyi +71 -65
- reflex/components/radix/themes/components/dialog.pyi +121 -120
- reflex/components/radix/themes/components/dropdown_menu.pyi +142 -141
- reflex/components/radix/themes/components/hover_card.pyi +68 -67
- reflex/components/radix/themes/components/icon_button.py +2 -1
- reflex/components/radix/themes/components/icon_button.pyi +18 -17
- reflex/components/radix/themes/components/inset.pyi +18 -17
- reflex/components/radix/themes/components/popover.pyi +73 -72
- reflex/components/radix/themes/components/progress.pyi +18 -17
- reflex/components/radix/themes/components/radio.pyi +18 -17
- reflex/components/radix/themes/components/radio_cards.pyi +35 -34
- reflex/components/radix/themes/components/radio_group.py +35 -31
- reflex/components/radix/themes/components/radio_group.pyi +73 -66
- reflex/components/radix/themes/components/scroll_area.pyi +18 -17
- reflex/components/radix/themes/components/segmented_control.pyi +35 -34
- reflex/components/radix/themes/components/select.py +2 -1
- reflex/components/radix/themes/components/select.pyi +155 -154
- reflex/components/radix/themes/components/separator.py +2 -3
- reflex/components/radix/themes/components/separator.pyi +18 -17
- reflex/components/radix/themes/components/skeleton.pyi +18 -17
- reflex/components/radix/themes/components/slider.py +2 -1
- reflex/components/radix/themes/components/slider.pyi +20 -19
- reflex/components/radix/themes/components/spinner.pyi +18 -17
- reflex/components/radix/themes/components/switch.pyi +19 -18
- reflex/components/radix/themes/components/table.pyi +114 -113
- reflex/components/radix/themes/components/tabs.pyi +84 -83
- reflex/components/radix/themes/components/text_area.pyi +21 -20
- reflex/components/radix/themes/components/text_field.py +0 -79
- reflex/components/radix/themes/components/text_field.pyi +57 -63
- reflex/components/radix/themes/components/tooltip.pyi +21 -20
- reflex/components/radix/themes/layout/base.pyi +18 -17
- reflex/components/radix/themes/layout/box.pyi +18 -17
- reflex/components/radix/themes/layout/center.pyi +18 -17
- reflex/components/radix/themes/layout/container.py +2 -3
- reflex/components/radix/themes/layout/container.pyi +18 -17
- reflex/components/radix/themes/layout/flex.pyi +18 -17
- reflex/components/radix/themes/layout/grid.pyi +18 -17
- reflex/components/radix/themes/layout/list.py +5 -4
- reflex/components/radix/themes/layout/list.pyi +86 -85
- reflex/components/radix/themes/layout/section.py +2 -3
- reflex/components/radix/themes/layout/section.pyi +18 -17
- reflex/components/radix/themes/layout/spacer.pyi +18 -17
- reflex/components/radix/themes/layout/stack.pyi +50 -49
- reflex/components/radix/themes/typography/blockquote.pyi +18 -17
- reflex/components/radix/themes/typography/code.pyi +18 -17
- reflex/components/radix/themes/typography/heading.pyi +18 -17
- reflex/components/radix/themes/typography/link.pyi +18 -17
- reflex/components/radix/themes/typography/text.pyi +114 -113
- reflex/components/react_player/audio.pyi +34 -33
- reflex/components/react_player/react_player.pyi +34 -33
- reflex/components/react_player/video.pyi +34 -33
- reflex/components/recharts/cartesian.py +23 -19
- reflex/components/recharts/cartesian.pyi +297 -296
- reflex/components/recharts/charts.py +6 -5
- reflex/components/recharts/charts.pyi +179 -178
- reflex/components/recharts/general.py +8 -7
- reflex/components/recharts/general.pyi +82 -81
- reflex/components/recharts/polar.py +14 -13
- reflex/components/recharts/polar.pyi +76 -75
- reflex/components/recharts/recharts.pyi +33 -33
- reflex/components/sonner/toast.py +30 -33
- reflex/components/sonner/toast.pyi +27 -25
- reflex/components/suneditor/editor.py +2 -1
- reflex/components/suneditor/editor.pyi +27 -26
- reflex/components/tags/iter_tag.py +16 -16
- reflex/components/tags/tag.py +8 -10
- reflex/constants/base.py +3 -1
- reflex/constants/event.py +1 -0
- reflex/event.py +89 -79
- reflex/experimental/__init__.py +25 -6
- reflex/experimental/client_state.py +34 -58
- reflex/experimental/hooks.py +13 -18
- reflex/experimental/layout.py +5 -5
- reflex/experimental/layout.pyi +84 -83
- reflex/{experimental/vars → ivars}/__init__.py +0 -1
- reflex/ivars/base.py +2180 -0
- reflex/ivars/function.py +200 -0
- reflex/ivars/number.py +1137 -0
- reflex/ivars/object.py +564 -0
- reflex/ivars/sequence.py +1601 -0
- reflex/model.py +22 -0
- reflex/reflex.py +4 -0
- reflex/state.py +388 -73
- reflex/style.py +52 -34
- reflex/testing.py +8 -3
- reflex/utils/exceptions.py +12 -0
- reflex/utils/exec.py +0 -14
- reflex/utils/format.py +74 -223
- reflex/utils/net.py +43 -0
- reflex/utils/path_ops.py +13 -1
- reflex/utils/prerequisites.py +46 -26
- reflex/utils/pyi_generator.py +5 -4
- reflex/utils/serializers.py +13 -31
- reflex/utils/types.py +44 -9
- reflex/vars.py +127 -2230
- {reflex-0.5.10a3.dist-info → reflex-0.6.0a1.dist-info}/METADATA +4 -6
- reflex-0.6.0a1.dist-info/RECORD +384 -0
- reflex/.templates/apps/demo/.gitignore +0 -4
- reflex/.templates/apps/demo/assets/favicon.ico +0 -0
- reflex/.templates/apps/demo/assets/github.svg +0 -10
- reflex/.templates/apps/demo/assets/icon.svg +0 -37
- reflex/.templates/apps/demo/assets/logo.svg +0 -68
- reflex/.templates/apps/demo/assets/paneleft.svg +0 -13
- reflex/.templates/apps/demo/code/__init__.py +0 -1
- reflex/.templates/apps/demo/code/demo.py +0 -127
- reflex/.templates/apps/demo/code/pages/__init__.py +0 -7
- reflex/.templates/apps/demo/code/pages/chatapp.py +0 -31
- reflex/.templates/apps/demo/code/pages/datatable.py +0 -360
- reflex/.templates/apps/demo/code/pages/forms.py +0 -257
- reflex/.templates/apps/demo/code/pages/graphing.py +0 -253
- reflex/.templates/apps/demo/code/pages/home.py +0 -56
- reflex/.templates/apps/demo/code/sidebar.py +0 -178
- reflex/.templates/apps/demo/code/state.py +0 -22
- reflex/.templates/apps/demo/code/states/form_state.py +0 -40
- reflex/.templates/apps/demo/code/states/pie_state.py +0 -47
- reflex/.templates/apps/demo/code/styles.py +0 -68
- reflex/.templates/apps/demo/code/webui/__init__.py +0 -0
- reflex/.templates/apps/demo/code/webui/components/__init__.py +0 -4
- reflex/.templates/apps/demo/code/webui/components/chat.py +0 -118
- reflex/.templates/apps/demo/code/webui/components/loading_icon.py +0 -19
- reflex/.templates/apps/demo/code/webui/components/modal.py +0 -56
- reflex/.templates/apps/demo/code/webui/components/navbar.py +0 -70
- reflex/.templates/apps/demo/code/webui/components/sidebar.py +0 -66
- reflex/.templates/apps/demo/code/webui/state.py +0 -146
- reflex/.templates/apps/demo/code/webui/styles.py +0 -88
- reflex/experimental/vars/base.py +0 -583
- reflex/experimental/vars/function.py +0 -290
- reflex/experimental/vars/number.py +0 -1458
- reflex/experimental/vars/object.py +0 -804
- reflex/experimental/vars/sequence.py +0 -1764
- reflex/utils/watch.py +0 -96
- reflex/vars.pyi +0 -218
- reflex-0.5.10a3.dist-info/RECORD +0 -413
- {reflex-0.5.10a3.dist-info → reflex-0.6.0a1.dist-info}/LICENSE +0 -0
- {reflex-0.5.10a3.dist-info → reflex-0.6.0a1.dist-info}/WHEEL +0 -0
- {reflex-0.5.10a3.dist-info → reflex-0.6.0a1.dist-info}/entry_points.txt +0 -0
reflex/state.py
CHANGED
|
@@ -11,6 +11,7 @@ import os
|
|
|
11
11
|
import uuid
|
|
12
12
|
from abc import ABC, abstractmethod
|
|
13
13
|
from collections import defaultdict
|
|
14
|
+
from pathlib import Path
|
|
14
15
|
from types import FunctionType, MethodType
|
|
15
16
|
from typing import (
|
|
16
17
|
TYPE_CHECKING,
|
|
@@ -23,6 +24,7 @@ from typing import (
|
|
|
23
24
|
Optional,
|
|
24
25
|
Sequence,
|
|
25
26
|
Set,
|
|
27
|
+
Tuple,
|
|
26
28
|
Type,
|
|
27
29
|
Union,
|
|
28
30
|
cast,
|
|
@@ -32,6 +34,13 @@ import dill
|
|
|
32
34
|
from sqlalchemy.orm import DeclarativeBase
|
|
33
35
|
|
|
34
36
|
from reflex.config import get_config
|
|
37
|
+
from reflex.ivars.base import (
|
|
38
|
+
DynamicRouteVar,
|
|
39
|
+
ImmutableComputedVar,
|
|
40
|
+
ImmutableVar,
|
|
41
|
+
immutable_computed_var,
|
|
42
|
+
is_computed_var,
|
|
43
|
+
)
|
|
35
44
|
|
|
36
45
|
try:
|
|
37
46
|
import pydantic.v1 as pydantic
|
|
@@ -51,19 +60,23 @@ from reflex.event import (
|
|
|
51
60
|
EventSpec,
|
|
52
61
|
fix_events,
|
|
53
62
|
)
|
|
54
|
-
from reflex.utils import console, format, prerequisites, types
|
|
55
|
-
from reflex.utils.exceptions import
|
|
63
|
+
from reflex.utils import console, format, path_ops, prerequisites, types
|
|
64
|
+
from reflex.utils.exceptions import (
|
|
65
|
+
DynamicRouteArgShadowsStateVar,
|
|
66
|
+
ImmutableStateError,
|
|
67
|
+
LockExpiredError,
|
|
68
|
+
)
|
|
56
69
|
from reflex.utils.exec import is_testing_env
|
|
57
70
|
from reflex.utils.serializers import SerializedType, serialize, serializer
|
|
58
71
|
from reflex.utils.types import override
|
|
59
|
-
from reflex.vars import
|
|
72
|
+
from reflex.vars import VarData
|
|
60
73
|
|
|
61
74
|
if TYPE_CHECKING:
|
|
62
75
|
from reflex.components.component import Component
|
|
63
76
|
|
|
64
77
|
|
|
65
78
|
Delta = Dict[str, Any]
|
|
66
|
-
var =
|
|
79
|
+
var = immutable_computed_var
|
|
67
80
|
|
|
68
81
|
|
|
69
82
|
# If the state is this large, it's considered a performance issue.
|
|
@@ -296,16 +309,16 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
296
309
|
"""The state of the app."""
|
|
297
310
|
|
|
298
311
|
# A map from the var name to the var.
|
|
299
|
-
vars: ClassVar[Dict[str,
|
|
312
|
+
vars: ClassVar[Dict[str, ImmutableVar]] = {}
|
|
300
313
|
|
|
301
314
|
# The base vars of the class.
|
|
302
|
-
base_vars: ClassVar[Dict[str,
|
|
315
|
+
base_vars: ClassVar[Dict[str, ImmutableVar]] = {}
|
|
303
316
|
|
|
304
317
|
# The computed vars of the class.
|
|
305
|
-
computed_vars: ClassVar[Dict[str,
|
|
318
|
+
computed_vars: ClassVar[Dict[str, ImmutableComputedVar]] = {}
|
|
306
319
|
|
|
307
320
|
# Vars inherited by the parent state.
|
|
308
|
-
inherited_vars: ClassVar[Dict[str,
|
|
321
|
+
inherited_vars: ClassVar[Dict[str, ImmutableVar]] = {}
|
|
309
322
|
|
|
310
323
|
# Backend base vars that are never sent to the client.
|
|
311
324
|
backend_vars: ClassVar[Dict[str, Any]] = {}
|
|
@@ -415,7 +428,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
415
428
|
return f"{self.__class__.__name__}({self.dict()})"
|
|
416
429
|
|
|
417
430
|
@classmethod
|
|
418
|
-
def _get_computed_vars(cls) -> list[
|
|
431
|
+
def _get_computed_vars(cls) -> list[ImmutableComputedVar]:
|
|
419
432
|
"""Helper function to get all computed vars of a instance.
|
|
420
433
|
|
|
421
434
|
Returns:
|
|
@@ -424,8 +437,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
424
437
|
return [
|
|
425
438
|
v
|
|
426
439
|
for mixin in cls._mixins() + [cls]
|
|
427
|
-
for v in mixin.__dict__.
|
|
428
|
-
if
|
|
440
|
+
for name, v in mixin.__dict__.items()
|
|
441
|
+
if is_computed_var(v) and name not in cls.inherited_vars
|
|
429
442
|
]
|
|
430
443
|
|
|
431
444
|
@classmethod
|
|
@@ -469,7 +482,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
469
482
|
cls._check_overridden_methods()
|
|
470
483
|
|
|
471
484
|
# Computed vars should not shadow builtin state props.
|
|
472
|
-
cls.
|
|
485
|
+
cls._check_overridden_basevars()
|
|
473
486
|
|
|
474
487
|
# Reset subclass tracking for this class.
|
|
475
488
|
cls.class_subclasses = set()
|
|
@@ -487,26 +500,17 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
487
500
|
if cls.get_name() in set(
|
|
488
501
|
c.get_name() for c in parent_state.class_subclasses
|
|
489
502
|
):
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
for c in parent_state.class_subclasses
|
|
496
|
-
if c.get_name() != cls.get_name()
|
|
497
|
-
)
|
|
498
|
-
else:
|
|
499
|
-
# During normal operation, subclasses cannot have the same name, even if they are
|
|
500
|
-
# defined in different modules.
|
|
501
|
-
raise StateValueError(
|
|
502
|
-
f"The substate class '{cls.get_name()}' has been defined multiple times. "
|
|
503
|
-
"Shadowing substate classes is not allowed."
|
|
504
|
-
)
|
|
503
|
+
# This should not happen, since we have added module prefix to state names in #3214
|
|
504
|
+
raise StateValueError(
|
|
505
|
+
f"The substate class '{cls.get_name()}' has been defined multiple times. "
|
|
506
|
+
"Shadowing substate classes is not allowed."
|
|
507
|
+
)
|
|
505
508
|
# Track this new subclass in the parent state's subclasses set.
|
|
506
509
|
parent_state.class_subclasses.add(cls)
|
|
507
510
|
|
|
508
511
|
# Get computed vars.
|
|
509
512
|
computed_vars = cls._get_computed_vars()
|
|
513
|
+
cls._check_overridden_computed_vars()
|
|
510
514
|
|
|
511
515
|
new_backend_vars = {
|
|
512
516
|
name: value
|
|
@@ -521,13 +525,18 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
521
525
|
|
|
522
526
|
# Set the base and computed vars.
|
|
523
527
|
cls.base_vars = {
|
|
524
|
-
f.name:
|
|
525
|
-
cls
|
|
526
|
-
|
|
528
|
+
f.name: ImmutableVar(
|
|
529
|
+
_var_name=format.format_state_name(cls.get_full_name()) + "." + f.name,
|
|
530
|
+
_var_type=f.outer_type_,
|
|
531
|
+
_var_data=VarData.from_state(cls),
|
|
532
|
+
).guess_type()
|
|
527
533
|
for f in cls.get_fields().values()
|
|
528
534
|
if f.name not in cls.get_skip_vars()
|
|
529
535
|
}
|
|
530
|
-
cls.computed_vars = {
|
|
536
|
+
cls.computed_vars = {
|
|
537
|
+
v._var_name: v._replace(merge_var_data=VarData.from_state(cls))
|
|
538
|
+
for v in computed_vars
|
|
539
|
+
}
|
|
531
540
|
cls.vars = {
|
|
532
541
|
**cls.inherited_vars,
|
|
533
542
|
**cls.base_vars,
|
|
@@ -548,12 +557,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
548
557
|
|
|
549
558
|
for mixin in cls._mixins():
|
|
550
559
|
for name, value in mixin.__dict__.items():
|
|
551
|
-
if
|
|
560
|
+
if name in cls.inherited_vars:
|
|
561
|
+
continue
|
|
562
|
+
if is_computed_var(value):
|
|
552
563
|
fget = cls._copy_fn(value.fget)
|
|
553
|
-
newcv = value._replace(fget=fget)
|
|
564
|
+
newcv = value._replace(fget=fget, _var_data=VarData.from_state(cls))
|
|
554
565
|
# cleanup refs to mixin cls in var_data
|
|
555
|
-
newcv._var_data = None
|
|
556
|
-
newcv._var_set_state(cls)
|
|
557
566
|
setattr(cls, name, newcv)
|
|
558
567
|
cls.computed_vars[newcv._var_name] = newcv
|
|
559
568
|
cls.vars[newcv._var_name] = newcv
|
|
@@ -579,6 +588,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
579
588
|
cls.event_handlers[name] = handler
|
|
580
589
|
setattr(cls, name, handler)
|
|
581
590
|
|
|
591
|
+
# Initialize per-class var dependency tracking.
|
|
592
|
+
cls._computed_var_dependencies = defaultdict(set)
|
|
593
|
+
cls._substate_var_dependencies = defaultdict(set)
|
|
582
594
|
cls._init_var_dependency_dicts()
|
|
583
595
|
|
|
584
596
|
@staticmethod
|
|
@@ -648,10 +660,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
648
660
|
Additional updates tracking dicts for vars and substates that always
|
|
649
661
|
need to be recomputed.
|
|
650
662
|
"""
|
|
651
|
-
# Initialize per-class var dependency tracking.
|
|
652
|
-
cls._computed_var_dependencies = defaultdict(set)
|
|
653
|
-
cls._substate_var_dependencies = defaultdict(set)
|
|
654
|
-
|
|
655
663
|
inherited_vars = set(cls.inherited_vars).union(
|
|
656
664
|
set(cls.inherited_backend_vars),
|
|
657
665
|
)
|
|
@@ -716,7 +724,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
716
724
|
)
|
|
717
725
|
|
|
718
726
|
@classmethod
|
|
719
|
-
def
|
|
727
|
+
def _check_overridden_basevars(cls):
|
|
720
728
|
"""Check for shadow base vars and raise error if any.
|
|
721
729
|
|
|
722
730
|
Raises:
|
|
@@ -728,6 +736,22 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
728
736
|
f"The computed var name `{computed_var_._var_name}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead"
|
|
729
737
|
)
|
|
730
738
|
|
|
739
|
+
@classmethod
|
|
740
|
+
def _check_overridden_computed_vars(cls) -> None:
|
|
741
|
+
"""Check for shadow computed vars and raise error if any.
|
|
742
|
+
|
|
743
|
+
Raises:
|
|
744
|
+
NameError: When a computed var shadows another.
|
|
745
|
+
"""
|
|
746
|
+
for name, cv in cls.__dict__.items():
|
|
747
|
+
if not is_computed_var(cv):
|
|
748
|
+
continue
|
|
749
|
+
name = cv._var_name
|
|
750
|
+
if name in cls.inherited_vars or name in cls.inherited_backend_vars:
|
|
751
|
+
raise NameError(
|
|
752
|
+
f"The computed var name `{cv._var_name}` shadows a var in {cls.__module__}.{cls.__name__}; use a different name instead"
|
|
753
|
+
)
|
|
754
|
+
|
|
731
755
|
@classmethod
|
|
732
756
|
def get_skip_vars(cls) -> set[str]:
|
|
733
757
|
"""Get the vars to skip when serializing.
|
|
@@ -847,7 +871,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
847
871
|
return getattr(substate, name)
|
|
848
872
|
|
|
849
873
|
@classmethod
|
|
850
|
-
def _init_var(cls, prop:
|
|
874
|
+
def _init_var(cls, prop: ImmutableVar):
|
|
851
875
|
"""Initialize a variable.
|
|
852
876
|
|
|
853
877
|
Args:
|
|
@@ -890,8 +914,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
890
914
|
)
|
|
891
915
|
|
|
892
916
|
# create the variable based on name and type
|
|
893
|
-
var =
|
|
894
|
-
|
|
917
|
+
var = ImmutableVar(
|
|
918
|
+
_var_name=format.format_state_name(cls.get_full_name()) + "." + name,
|
|
919
|
+
_var_type=type_,
|
|
920
|
+
_var_data=VarData.from_state(cls),
|
|
921
|
+
).guess_type()
|
|
895
922
|
|
|
896
923
|
# add the pydantic field dynamically (must be done before _init_var)
|
|
897
924
|
cls.add_field(var, default_value)
|
|
@@ -910,13 +937,18 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
910
937
|
cls._init_var_dependency_dicts()
|
|
911
938
|
|
|
912
939
|
@classmethod
|
|
913
|
-
def _set_var(cls, prop:
|
|
940
|
+
def _set_var(cls, prop: ImmutableVar):
|
|
914
941
|
"""Set the var as a class member.
|
|
915
942
|
|
|
916
943
|
Args:
|
|
917
944
|
prop: The var instance to set.
|
|
918
945
|
"""
|
|
919
|
-
|
|
946
|
+
acutal_var_name = (
|
|
947
|
+
prop._var_name
|
|
948
|
+
if "." not in prop._var_name
|
|
949
|
+
else prop._var_name.split(".")[-1]
|
|
950
|
+
)
|
|
951
|
+
setattr(cls, acutal_var_name, prop)
|
|
920
952
|
|
|
921
953
|
@classmethod
|
|
922
954
|
def _create_event_handler(cls, fn):
|
|
@@ -936,7 +968,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
936
968
|
cls.setvar = cls.event_handlers["setvar"] = EventHandlerSetVar(state_cls=cls)
|
|
937
969
|
|
|
938
970
|
@classmethod
|
|
939
|
-
def _create_setter(cls, prop:
|
|
971
|
+
def _create_setter(cls, prop: ImmutableVar):
|
|
940
972
|
"""Create a setter for the var.
|
|
941
973
|
|
|
942
974
|
Args:
|
|
@@ -949,14 +981,17 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
949
981
|
setattr(cls, setter_name, event_handler)
|
|
950
982
|
|
|
951
983
|
@classmethod
|
|
952
|
-
def _set_default_value(cls, prop:
|
|
984
|
+
def _set_default_value(cls, prop: ImmutableVar):
|
|
953
985
|
"""Set the default value for the var.
|
|
954
986
|
|
|
955
987
|
Args:
|
|
956
988
|
prop: The var to set the default value for.
|
|
957
989
|
"""
|
|
958
990
|
# Get the pydantic field for the var.
|
|
959
|
-
|
|
991
|
+
if "." in prop._var_name:
|
|
992
|
+
field = cls.get_fields()[prop._var_name.split(".")[-1]]
|
|
993
|
+
else:
|
|
994
|
+
field = cls.get_fields()[prop._var_name]
|
|
960
995
|
if field.required:
|
|
961
996
|
default_value = prop.get_default_value()
|
|
962
997
|
if default_value is not None:
|
|
@@ -968,7 +1003,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
968
1003
|
and not types.is_optional(prop._var_type)
|
|
969
1004
|
):
|
|
970
1005
|
# Ensure frontend uses null coalescing when accessing.
|
|
971
|
-
prop
|
|
1006
|
+
object.__setattr__(prop, "_var_type", Optional[prop._var_type])
|
|
972
1007
|
|
|
973
1008
|
@staticmethod
|
|
974
1009
|
def _get_base_functions() -> dict[str, FunctionType]:
|
|
@@ -990,20 +1025,22 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
990
1025
|
Args:
|
|
991
1026
|
args: a dict of args
|
|
992
1027
|
"""
|
|
1028
|
+
if not args:
|
|
1029
|
+
return
|
|
1030
|
+
|
|
1031
|
+
cls._check_overwritten_dynamic_args(list(args.keys()))
|
|
993
1032
|
|
|
994
1033
|
def argsingle_factory(param):
|
|
995
|
-
@ComputedVar
|
|
996
1034
|
def inner_func(self) -> str:
|
|
997
1035
|
return self.router.page.params.get(param, "")
|
|
998
1036
|
|
|
999
|
-
return inner_func
|
|
1037
|
+
return DynamicRouteVar(fget=inner_func, cache=True)
|
|
1000
1038
|
|
|
1001
1039
|
def arglist_factory(param):
|
|
1002
|
-
|
|
1003
|
-
def inner_func(self) -> List:
|
|
1040
|
+
def inner_func(self) -> List[str]:
|
|
1004
1041
|
return self.router.page.params.get(param, [])
|
|
1005
1042
|
|
|
1006
|
-
return inner_func
|
|
1043
|
+
return DynamicRouteVar(fget=inner_func, cache=True)
|
|
1007
1044
|
|
|
1008
1045
|
for param, value in args.items():
|
|
1009
1046
|
if value == constants.RouteArgType.SINGLE:
|
|
@@ -1012,13 +1049,37 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1012
1049
|
func = arglist_factory(param)
|
|
1013
1050
|
else:
|
|
1014
1051
|
continue
|
|
1015
|
-
# to allow passing as a prop
|
|
1016
|
-
func
|
|
1017
|
-
cls.vars[param] = cls.computed_vars[param] = func._var_set_state(cls) # type: ignore
|
|
1052
|
+
# to allow passing as a prop, evade python frozen rules (bad practice)
|
|
1053
|
+
object.__setattr__(func, "_var_name", param)
|
|
1054
|
+
# cls.vars[param] = cls.computed_vars[param] = func._var_set_state(cls) # type: ignore
|
|
1055
|
+
cls.vars[param] = cls.computed_vars[param] = func._replace(
|
|
1056
|
+
_var_data=VarData.from_state(cls)
|
|
1057
|
+
)
|
|
1018
1058
|
setattr(cls, param, func)
|
|
1019
1059
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1060
|
+
# Reinitialize dependency tracking dicts.
|
|
1061
|
+
cls._init_var_dependency_dicts()
|
|
1062
|
+
|
|
1063
|
+
@classmethod
|
|
1064
|
+
def _check_overwritten_dynamic_args(cls, args: list[str]):
|
|
1065
|
+
"""Check if dynamic args are shadowing existing vars. Recursively checks all child states.
|
|
1066
|
+
|
|
1067
|
+
Args:
|
|
1068
|
+
args: a dict of args
|
|
1069
|
+
|
|
1070
|
+
Raises:
|
|
1071
|
+
DynamicRouteArgShadowsStateVar: If a dynamic arg is shadowing an existing var.
|
|
1072
|
+
"""
|
|
1073
|
+
for arg in args:
|
|
1074
|
+
if (
|
|
1075
|
+
arg in cls.computed_vars
|
|
1076
|
+
and not isinstance(cls.computed_vars[arg], DynamicRouteVar)
|
|
1077
|
+
) or arg in cls.base_vars:
|
|
1078
|
+
raise DynamicRouteArgShadowsStateVar(
|
|
1079
|
+
f"Dynamic route arg '{arg}' is shadowing an existing var in {cls.__module__}.{cls.__name__}"
|
|
1080
|
+
)
|
|
1081
|
+
for substate in cls.get_substates():
|
|
1082
|
+
substate._check_overwritten_dynamic_args(args)
|
|
1022
1083
|
|
|
1023
1084
|
def __getattribute__(self, name: str) -> Any:
|
|
1024
1085
|
"""Get the state var.
|
|
@@ -1769,12 +1830,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1769
1830
|
prop_name: self.get_value(getattr(self, prop_name))
|
|
1770
1831
|
for prop_name in self.base_vars
|
|
1771
1832
|
}
|
|
1772
|
-
if initial:
|
|
1833
|
+
if initial and include_computed:
|
|
1773
1834
|
computed_vars = {
|
|
1774
1835
|
# Include initial computed vars.
|
|
1775
1836
|
prop_name: (
|
|
1776
1837
|
cv._initial_value
|
|
1777
|
-
if
|
|
1838
|
+
if is_computed_var(cv)
|
|
1778
1839
|
and not isinstance(cv._initial_value, types.Unset)
|
|
1779
1840
|
else self.get_value(getattr(self, prop_name))
|
|
1780
1841
|
)
|
|
@@ -2272,11 +2333,12 @@ class StateProxy(wrapt.ObjectProxy):
|
|
|
2272
2333
|
Returns:
|
|
2273
2334
|
The state update.
|
|
2274
2335
|
"""
|
|
2336
|
+
original_mutable = self._self_mutable
|
|
2275
2337
|
self._self_mutable = True
|
|
2276
2338
|
try:
|
|
2277
2339
|
return self.__wrapped__._as_state_update(*args, **kwargs)
|
|
2278
2340
|
finally:
|
|
2279
|
-
self._self_mutable =
|
|
2341
|
+
self._self_mutable = original_mutable
|
|
2280
2342
|
|
|
2281
2343
|
|
|
2282
2344
|
class StateUpdate(Base):
|
|
@@ -2318,7 +2380,7 @@ class StateManager(Base, ABC):
|
|
|
2318
2380
|
token_expiration=config.redis_token_expiration,
|
|
2319
2381
|
lock_expiration=config.redis_lock_expiration,
|
|
2320
2382
|
)
|
|
2321
|
-
return
|
|
2383
|
+
return StateManagerDisk(state=state)
|
|
2322
2384
|
|
|
2323
2385
|
@abstractmethod
|
|
2324
2386
|
async def get_state(self, token: str) -> BaseState:
|
|
@@ -2425,6 +2487,266 @@ class StateManagerMemory(StateManager):
|
|
|
2425
2487
|
await self.set_state(token, state)
|
|
2426
2488
|
|
|
2427
2489
|
|
|
2490
|
+
def _default_token_expiration() -> int:
|
|
2491
|
+
"""Get the default token expiration time.
|
|
2492
|
+
|
|
2493
|
+
Returns:
|
|
2494
|
+
The default token expiration time.
|
|
2495
|
+
"""
|
|
2496
|
+
return get_config().redis_token_expiration
|
|
2497
|
+
|
|
2498
|
+
|
|
2499
|
+
def _serialize_type(type_: Any) -> str:
|
|
2500
|
+
"""Serialize a type.
|
|
2501
|
+
|
|
2502
|
+
Args:
|
|
2503
|
+
type_: The type to serialize.
|
|
2504
|
+
|
|
2505
|
+
Returns:
|
|
2506
|
+
The serialized type.
|
|
2507
|
+
"""
|
|
2508
|
+
if not inspect.isclass(type_):
|
|
2509
|
+
return f"{type_}"
|
|
2510
|
+
return f"{type_.__module__}.{type_.__qualname__}"
|
|
2511
|
+
|
|
2512
|
+
|
|
2513
|
+
def state_to_schema(
|
|
2514
|
+
state: BaseState,
|
|
2515
|
+
) -> List[
|
|
2516
|
+
Tuple[
|
|
2517
|
+
str,
|
|
2518
|
+
str,
|
|
2519
|
+
Any,
|
|
2520
|
+
Union[bool, None],
|
|
2521
|
+
]
|
|
2522
|
+
]:
|
|
2523
|
+
"""Convert a state to a schema.
|
|
2524
|
+
|
|
2525
|
+
Args:
|
|
2526
|
+
state: The state to convert to a schema.
|
|
2527
|
+
|
|
2528
|
+
Returns:
|
|
2529
|
+
The schema.
|
|
2530
|
+
"""
|
|
2531
|
+
return list(
|
|
2532
|
+
sorted(
|
|
2533
|
+
(
|
|
2534
|
+
field_name,
|
|
2535
|
+
model_field.name,
|
|
2536
|
+
_serialize_type(model_field.type_),
|
|
2537
|
+
(
|
|
2538
|
+
model_field.required
|
|
2539
|
+
if isinstance(model_field.required, bool)
|
|
2540
|
+
else None
|
|
2541
|
+
),
|
|
2542
|
+
)
|
|
2543
|
+
for field_name, model_field in state.__fields__.items()
|
|
2544
|
+
)
|
|
2545
|
+
)
|
|
2546
|
+
|
|
2547
|
+
|
|
2548
|
+
def reset_disk_state_manager():
|
|
2549
|
+
"""Reset the disk state manager."""
|
|
2550
|
+
states_directory = prerequisites.get_web_dir() / constants.Dirs.STATES
|
|
2551
|
+
if states_directory.exists():
|
|
2552
|
+
for path in states_directory.iterdir():
|
|
2553
|
+
path.unlink()
|
|
2554
|
+
|
|
2555
|
+
|
|
2556
|
+
class StateManagerDisk(StateManager):
|
|
2557
|
+
"""A state manager that stores states in memory."""
|
|
2558
|
+
|
|
2559
|
+
# The mapping of client ids to states.
|
|
2560
|
+
states: Dict[str, BaseState] = {}
|
|
2561
|
+
|
|
2562
|
+
# The mutex ensures the dict of mutexes is updated exclusively
|
|
2563
|
+
_state_manager_lock = asyncio.Lock()
|
|
2564
|
+
|
|
2565
|
+
# The dict of mutexes for each client
|
|
2566
|
+
_states_locks: Dict[str, asyncio.Lock] = pydantic.PrivateAttr({})
|
|
2567
|
+
|
|
2568
|
+
# The token expiration time (s).
|
|
2569
|
+
token_expiration: int = pydantic.Field(default_factory=_default_token_expiration)
|
|
2570
|
+
|
|
2571
|
+
class Config:
|
|
2572
|
+
"""The Pydantic config."""
|
|
2573
|
+
|
|
2574
|
+
fields = {
|
|
2575
|
+
"_states_locks": {"exclude": True},
|
|
2576
|
+
}
|
|
2577
|
+
keep_untouched = (functools.cached_property,)
|
|
2578
|
+
|
|
2579
|
+
def __init__(self, state: Type[BaseState]):
|
|
2580
|
+
"""Create a new state manager.
|
|
2581
|
+
|
|
2582
|
+
Args:
|
|
2583
|
+
state: The state class to use.
|
|
2584
|
+
"""
|
|
2585
|
+
super().__init__(state=state)
|
|
2586
|
+
|
|
2587
|
+
path_ops.mkdir(self.states_directory)
|
|
2588
|
+
|
|
2589
|
+
self._purge_expired_states()
|
|
2590
|
+
|
|
2591
|
+
@functools.cached_property
|
|
2592
|
+
def states_directory(self) -> Path:
|
|
2593
|
+
"""Get the states directory.
|
|
2594
|
+
|
|
2595
|
+
Returns:
|
|
2596
|
+
The states directory.
|
|
2597
|
+
"""
|
|
2598
|
+
return prerequisites.get_web_dir() / constants.Dirs.STATES
|
|
2599
|
+
|
|
2600
|
+
def _purge_expired_states(self):
|
|
2601
|
+
"""Purge expired states from the disk."""
|
|
2602
|
+
import time
|
|
2603
|
+
|
|
2604
|
+
for path in path_ops.ls(self.states_directory):
|
|
2605
|
+
# check path is a pickle file
|
|
2606
|
+
if path.suffix != ".pkl":
|
|
2607
|
+
continue
|
|
2608
|
+
|
|
2609
|
+
# load last edited field from file
|
|
2610
|
+
last_edited = path.stat().st_mtime
|
|
2611
|
+
|
|
2612
|
+
# check if the file is older than the token expiration time
|
|
2613
|
+
if time.time() - last_edited > self.token_expiration:
|
|
2614
|
+
# remove the file
|
|
2615
|
+
path.unlink()
|
|
2616
|
+
|
|
2617
|
+
def token_path(self, token: str) -> Path:
|
|
2618
|
+
"""Get the path for a token.
|
|
2619
|
+
|
|
2620
|
+
Args:
|
|
2621
|
+
token: The token to get the path for.
|
|
2622
|
+
|
|
2623
|
+
Returns:
|
|
2624
|
+
The path for the token.
|
|
2625
|
+
"""
|
|
2626
|
+
return (self.states_directory / f"{token}.pkl").absolute()
|
|
2627
|
+
|
|
2628
|
+
async def load_state(self, token: str, root_state: BaseState) -> BaseState:
|
|
2629
|
+
"""Load a state object based on the provided token.
|
|
2630
|
+
|
|
2631
|
+
Args:
|
|
2632
|
+
token: The token used to identify the state object.
|
|
2633
|
+
root_state: The root state object.
|
|
2634
|
+
|
|
2635
|
+
Returns:
|
|
2636
|
+
The loaded state object.
|
|
2637
|
+
"""
|
|
2638
|
+
if token in self.states:
|
|
2639
|
+
return self.states[token]
|
|
2640
|
+
|
|
2641
|
+
client_token, substate_address = _split_substate_key(token)
|
|
2642
|
+
|
|
2643
|
+
token_path = self.token_path(token)
|
|
2644
|
+
|
|
2645
|
+
if token_path.exists():
|
|
2646
|
+
try:
|
|
2647
|
+
with token_path.open(mode="rb") as file:
|
|
2648
|
+
(substate_schema, substate) = dill.load(file)
|
|
2649
|
+
if substate_schema == state_to_schema(substate):
|
|
2650
|
+
await self.populate_substates(client_token, substate, root_state)
|
|
2651
|
+
return substate
|
|
2652
|
+
except Exception:
|
|
2653
|
+
pass
|
|
2654
|
+
|
|
2655
|
+
return root_state.get_substate(substate_address.split(".")[1:])
|
|
2656
|
+
|
|
2657
|
+
async def populate_substates(
|
|
2658
|
+
self, client_token: str, state: BaseState, root_state: BaseState
|
|
2659
|
+
):
|
|
2660
|
+
"""Populate the substates of a state object.
|
|
2661
|
+
|
|
2662
|
+
Args:
|
|
2663
|
+
client_token: The client token.
|
|
2664
|
+
state: The state object to populate.
|
|
2665
|
+
root_state: The root state object.
|
|
2666
|
+
"""
|
|
2667
|
+
for substate in state.get_substates():
|
|
2668
|
+
substate_token = _substate_key(client_token, substate)
|
|
2669
|
+
|
|
2670
|
+
substate = await self.load_state(substate_token, root_state)
|
|
2671
|
+
|
|
2672
|
+
state.substates[substate.get_name()] = substate
|
|
2673
|
+
substate.parent_state = state
|
|
2674
|
+
|
|
2675
|
+
@override
|
|
2676
|
+
async def get_state(
|
|
2677
|
+
self,
|
|
2678
|
+
token: str,
|
|
2679
|
+
) -> BaseState:
|
|
2680
|
+
"""Get the state for a token.
|
|
2681
|
+
|
|
2682
|
+
Args:
|
|
2683
|
+
token: The token to get the state for.
|
|
2684
|
+
|
|
2685
|
+
Returns:
|
|
2686
|
+
The state for the token.
|
|
2687
|
+
"""
|
|
2688
|
+
client_token, substate_address = _split_substate_key(token)
|
|
2689
|
+
|
|
2690
|
+
root_state_token = _substate_key(client_token, substate_address.split(".")[0])
|
|
2691
|
+
|
|
2692
|
+
return await self.load_state(
|
|
2693
|
+
root_state_token, self.state(_reflex_internal_init=True)
|
|
2694
|
+
)
|
|
2695
|
+
|
|
2696
|
+
async def set_state_for_substate(self, client_token: str, substate: BaseState):
|
|
2697
|
+
"""Set the state for a substate.
|
|
2698
|
+
|
|
2699
|
+
Args:
|
|
2700
|
+
client_token: The client token.
|
|
2701
|
+
substate: The substate to set.
|
|
2702
|
+
"""
|
|
2703
|
+
substate_token = _substate_key(client_token, substate)
|
|
2704
|
+
|
|
2705
|
+
self.states[substate_token] = substate
|
|
2706
|
+
|
|
2707
|
+
state_dilled = dill.dumps((state_to_schema(substate), substate))
|
|
2708
|
+
if not self.states_directory.exists():
|
|
2709
|
+
self.states_directory.mkdir(parents=True, exist_ok=True)
|
|
2710
|
+
self.token_path(substate_token).write_bytes(state_dilled)
|
|
2711
|
+
|
|
2712
|
+
for substate_substate in substate.substates.values():
|
|
2713
|
+
await self.set_state_for_substate(client_token, substate_substate)
|
|
2714
|
+
|
|
2715
|
+
@override
|
|
2716
|
+
async def set_state(self, token: str, state: BaseState):
|
|
2717
|
+
"""Set the state for a token.
|
|
2718
|
+
|
|
2719
|
+
Args:
|
|
2720
|
+
token: The token to set the state for.
|
|
2721
|
+
state: The state to set.
|
|
2722
|
+
"""
|
|
2723
|
+
client_token, substate = _split_substate_key(token)
|
|
2724
|
+
await self.set_state_for_substate(client_token, state)
|
|
2725
|
+
|
|
2726
|
+
@override
|
|
2727
|
+
@contextlib.asynccontextmanager
|
|
2728
|
+
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
|
|
2729
|
+
"""Modify the state for a token while holding exclusive lock.
|
|
2730
|
+
|
|
2731
|
+
Args:
|
|
2732
|
+
token: The token to modify the state for.
|
|
2733
|
+
|
|
2734
|
+
Yields:
|
|
2735
|
+
The state for the token.
|
|
2736
|
+
"""
|
|
2737
|
+
# Memory state manager ignores the substate suffix and always returns the top-level state.
|
|
2738
|
+
client_token, substate = _split_substate_key(token)
|
|
2739
|
+
if client_token not in self._states_locks:
|
|
2740
|
+
async with self._state_manager_lock:
|
|
2741
|
+
if client_token not in self._states_locks:
|
|
2742
|
+
self._states_locks[client_token] = asyncio.Lock()
|
|
2743
|
+
|
|
2744
|
+
async with self._states_locks[client_token]:
|
|
2745
|
+
state = await self.get_state(token)
|
|
2746
|
+
yield state
|
|
2747
|
+
await self.set_state(token, state)
|
|
2748
|
+
|
|
2749
|
+
|
|
2428
2750
|
# Workaround https://github.com/cloudpipe/cloudpickle/issues/408 for dynamic pydantic classes
|
|
2429
2751
|
if not isinstance(State.validate.__func__, FunctionType):
|
|
2430
2752
|
cython_function_or_method = type(State.validate.__func__)
|
|
@@ -2453,15 +2775,6 @@ def _default_lock_expiration() -> int:
|
|
|
2453
2775
|
return get_config().redis_lock_expiration
|
|
2454
2776
|
|
|
2455
2777
|
|
|
2456
|
-
def _default_token_expiration() -> int:
|
|
2457
|
-
"""Get the default token expiration time.
|
|
2458
|
-
|
|
2459
|
-
Returns:
|
|
2460
|
-
The default token expiration time.
|
|
2461
|
-
"""
|
|
2462
|
-
return get_config().redis_token_expiration
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
2778
|
class StateManagerRedis(StateManager):
|
|
2466
2779
|
"""A state manager that stores states in redis."""
|
|
2467
2780
|
|
|
@@ -3340,5 +3653,7 @@ def reload_state_module(
|
|
|
3340
3653
|
if subclass.__module__ == module and module is not None:
|
|
3341
3654
|
state.class_subclasses.remove(subclass)
|
|
3342
3655
|
state._always_dirty_substates.discard(subclass.get_name())
|
|
3343
|
-
|
|
3656
|
+
state._computed_var_dependencies = defaultdict(set)
|
|
3657
|
+
state._substate_var_dependencies = defaultdict(set)
|
|
3658
|
+
state._init_var_dependency_dicts()
|
|
3344
3659
|
state.get_class_substate.cache_clear()
|