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/ivars/base.py
ADDED
|
@@ -0,0 +1,2180 @@
|
|
|
1
|
+
"""Collection of base classes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import contextlib
|
|
6
|
+
import dataclasses
|
|
7
|
+
import datetime
|
|
8
|
+
import dis
|
|
9
|
+
import functools
|
|
10
|
+
import inspect
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from types import CodeType, FunctionType
|
|
14
|
+
from typing import (
|
|
15
|
+
TYPE_CHECKING,
|
|
16
|
+
Any,
|
|
17
|
+
Callable,
|
|
18
|
+
Dict,
|
|
19
|
+
Generic,
|
|
20
|
+
List,
|
|
21
|
+
Literal,
|
|
22
|
+
NoReturn,
|
|
23
|
+
Optional,
|
|
24
|
+
Set,
|
|
25
|
+
Tuple,
|
|
26
|
+
Type,
|
|
27
|
+
TypeVar,
|
|
28
|
+
Union,
|
|
29
|
+
cast,
|
|
30
|
+
get_args,
|
|
31
|
+
overload,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from typing_extensions import ParamSpec, TypeGuard, deprecated, get_type_hints, override
|
|
35
|
+
|
|
36
|
+
from reflex import constants
|
|
37
|
+
from reflex.base import Base
|
|
38
|
+
from reflex.utils import console, imports, serializers, types
|
|
39
|
+
from reflex.utils.exceptions import (
|
|
40
|
+
VarAttributeError,
|
|
41
|
+
VarDependencyError,
|
|
42
|
+
VarTypeError,
|
|
43
|
+
VarValueError,
|
|
44
|
+
)
|
|
45
|
+
from reflex.utils.format import format_state_name
|
|
46
|
+
from reflex.utils.types import GenericType, Self, get_origin
|
|
47
|
+
from reflex.vars import (
|
|
48
|
+
REPLACED_NAMES,
|
|
49
|
+
Var,
|
|
50
|
+
VarData,
|
|
51
|
+
_decode_var_immutable,
|
|
52
|
+
_extract_var_data,
|
|
53
|
+
_global_vars,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if TYPE_CHECKING:
|
|
57
|
+
from reflex.state import BaseState
|
|
58
|
+
|
|
59
|
+
from .function import FunctionVar, ToFunctionOperation
|
|
60
|
+
from .number import (
|
|
61
|
+
BooleanVar,
|
|
62
|
+
NumberVar,
|
|
63
|
+
ToBooleanVarOperation,
|
|
64
|
+
ToNumberVarOperation,
|
|
65
|
+
)
|
|
66
|
+
from .object import ObjectVar, ToObjectOperation
|
|
67
|
+
from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
VAR_TYPE = TypeVar("VAR_TYPE")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclasses.dataclass(
|
|
74
|
+
eq=False,
|
|
75
|
+
frozen=True,
|
|
76
|
+
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
|
77
|
+
)
|
|
78
|
+
class ImmutableVar(Var, Generic[VAR_TYPE]):
|
|
79
|
+
"""Base class for immutable vars."""
|
|
80
|
+
|
|
81
|
+
# The name of the var.
|
|
82
|
+
_var_name: str = dataclasses.field()
|
|
83
|
+
|
|
84
|
+
# The type of the var.
|
|
85
|
+
_var_type: types.GenericType = dataclasses.field(default=Any)
|
|
86
|
+
|
|
87
|
+
# Extra metadata associated with the Var
|
|
88
|
+
_var_data: Optional[VarData] = dataclasses.field(default=None)
|
|
89
|
+
|
|
90
|
+
def __str__(self) -> str:
|
|
91
|
+
"""String representation of the var. Guaranteed to be a valid Javascript expression.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
The name of the var.
|
|
95
|
+
"""
|
|
96
|
+
return self._var_name
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def _var_is_local(self) -> bool:
|
|
100
|
+
"""Whether this is a local javascript variable.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
False
|
|
104
|
+
"""
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def _var_is_string(self) -> bool:
|
|
109
|
+
"""Whether the var is a string literal.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
False
|
|
113
|
+
"""
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def __post_init__(self):
|
|
117
|
+
"""Post-initialize the var."""
|
|
118
|
+
# Decode any inline Var markup and apply it to the instance
|
|
119
|
+
_var_data, _var_name = _decode_var_immutable(self._var_name)
|
|
120
|
+
|
|
121
|
+
if _var_data or _var_name != self._var_name:
|
|
122
|
+
self.__init__(
|
|
123
|
+
_var_name=_var_name,
|
|
124
|
+
_var_type=self._var_type,
|
|
125
|
+
_var_data=VarData.merge(self._var_data, _var_data),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def __hash__(self) -> int:
|
|
129
|
+
"""Define a hash function for the var.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
The hash of the var.
|
|
133
|
+
"""
|
|
134
|
+
return hash((self._var_name, self._var_type, self._var_data))
|
|
135
|
+
|
|
136
|
+
def _get_all_var_data(self) -> VarData | None:
|
|
137
|
+
"""Get all VarData associated with the Var.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
The VarData of the components and all of its children.
|
|
141
|
+
"""
|
|
142
|
+
return self._var_data
|
|
143
|
+
|
|
144
|
+
def equals(self, other: ImmutableVar) -> bool:
|
|
145
|
+
"""Check if two vars are equal.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
other: The other var to compare.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Whether the vars are equal.
|
|
152
|
+
"""
|
|
153
|
+
return (
|
|
154
|
+
self._var_name == other._var_name
|
|
155
|
+
and self._var_type == other._var_type
|
|
156
|
+
and self._get_all_var_data() == other._get_all_var_data()
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def _replace(self, merge_var_data=None, **kwargs: Any):
|
|
160
|
+
"""Make a copy of this Var with updated fields.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
merge_var_data: VarData to merge into the existing VarData.
|
|
164
|
+
**kwargs: Var fields to update.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
A new ImmutableVar with the updated fields overwriting the corresponding fields in this Var.
|
|
168
|
+
|
|
169
|
+
Raises:
|
|
170
|
+
TypeError: If _var_is_local, _var_is_string, or _var_full_name_needs_state_prefix is not None.
|
|
171
|
+
"""
|
|
172
|
+
if kwargs.get("_var_is_local", False) is not False:
|
|
173
|
+
raise TypeError(
|
|
174
|
+
"The _var_is_local argument is not supported for ImmutableVar."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if kwargs.get("_var_is_string", False) is not False:
|
|
178
|
+
raise TypeError(
|
|
179
|
+
"The _var_is_string argument is not supported for ImmutableVar."
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if kwargs.get("_var_full_name_needs_state_prefix", False) is not False:
|
|
183
|
+
raise TypeError(
|
|
184
|
+
"The _var_full_name_needs_state_prefix argument is not supported for ImmutableVar."
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
return dataclasses.replace(
|
|
188
|
+
self,
|
|
189
|
+
_var_data=VarData.merge(
|
|
190
|
+
kwargs.get("_var_data", self._var_data), merge_var_data
|
|
191
|
+
),
|
|
192
|
+
**kwargs,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def create(
|
|
197
|
+
cls,
|
|
198
|
+
value: Any,
|
|
199
|
+
_var_is_local: bool | None = None,
|
|
200
|
+
_var_is_string: bool | None = None,
|
|
201
|
+
_var_data: VarData | None = None,
|
|
202
|
+
) -> ImmutableVar | None:
|
|
203
|
+
"""Create a var from a value.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
value: The value to create the var from.
|
|
207
|
+
_var_is_local: Whether the var is local. Deprecated.
|
|
208
|
+
_var_is_string: Whether the var is a string literal. Deprecated.
|
|
209
|
+
_var_data: Additional hooks and imports associated with the Var.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
The var.
|
|
213
|
+
|
|
214
|
+
Raises:
|
|
215
|
+
VarTypeError: If the value is JSON-unserializable.
|
|
216
|
+
TypeError: If _var_is_local or _var_is_string is not None.
|
|
217
|
+
"""
|
|
218
|
+
if _var_is_local is not None:
|
|
219
|
+
raise TypeError(
|
|
220
|
+
"The _var_is_local argument is not supported for ImmutableVar."
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if _var_is_string is not None:
|
|
224
|
+
raise TypeError(
|
|
225
|
+
"The _var_is_string argument is not supported for ImmutableVar."
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
from reflex.utils import format
|
|
229
|
+
|
|
230
|
+
# Check for none values.
|
|
231
|
+
if value is None:
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
# If the value is already a var, do nothing.
|
|
235
|
+
if isinstance(value, ImmutableVar):
|
|
236
|
+
return value
|
|
237
|
+
|
|
238
|
+
# Try to pull the imports and hooks from contained values.
|
|
239
|
+
if not isinstance(value, str):
|
|
240
|
+
_var_data = VarData.merge(*_extract_var_data(value), _var_data)
|
|
241
|
+
|
|
242
|
+
# Try to serialize the value.
|
|
243
|
+
type_ = type(value)
|
|
244
|
+
if type_ in types.JSONType:
|
|
245
|
+
name = value
|
|
246
|
+
else:
|
|
247
|
+
name, _serialized_type = serializers.serialize(value, get_type=True)
|
|
248
|
+
if name is None:
|
|
249
|
+
raise VarTypeError(
|
|
250
|
+
f"No JSON serializer found for var {value} of type {type_}."
|
|
251
|
+
)
|
|
252
|
+
name = name if isinstance(name, str) else format.json_dumps(name)
|
|
253
|
+
|
|
254
|
+
return cls(
|
|
255
|
+
_var_name=name,
|
|
256
|
+
_var_type=type_,
|
|
257
|
+
_var_data=_var_data,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
@classmethod
|
|
261
|
+
def create_safe(
|
|
262
|
+
cls,
|
|
263
|
+
value: Any,
|
|
264
|
+
_var_is_local: bool | None = None,
|
|
265
|
+
_var_is_string: bool | None = None,
|
|
266
|
+
_var_data: VarData | None = None,
|
|
267
|
+
) -> ImmutableVar:
|
|
268
|
+
"""Create a var from a value, asserting that it is not None.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
value: The value to create the var from.
|
|
272
|
+
_var_is_local: Whether the var is local. Deprecated.
|
|
273
|
+
_var_is_string: Whether the var is a string literal. Deprecated.
|
|
274
|
+
_var_data: Additional hooks and imports associated with the Var.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
The var.
|
|
278
|
+
"""
|
|
279
|
+
var = cls.create(
|
|
280
|
+
value,
|
|
281
|
+
_var_is_local=_var_is_local,
|
|
282
|
+
_var_is_string=_var_is_string,
|
|
283
|
+
_var_data=_var_data,
|
|
284
|
+
)
|
|
285
|
+
assert var is not None
|
|
286
|
+
return var
|
|
287
|
+
|
|
288
|
+
def __format__(self, format_spec: str) -> str:
|
|
289
|
+
"""Format the var into a Javascript equivalent to an f-string.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
format_spec: The format specifier (Ignored for now).
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
The formatted var.
|
|
296
|
+
"""
|
|
297
|
+
hashed_var = hash(self)
|
|
298
|
+
|
|
299
|
+
_global_vars[hashed_var] = self
|
|
300
|
+
|
|
301
|
+
# Encode the _var_data into the formatted output for tracking purposes.
|
|
302
|
+
return f"{constants.REFLEX_VAR_OPENING_TAG}{hashed_var}{constants.REFLEX_VAR_CLOSING_TAG}{self._var_name}"
|
|
303
|
+
|
|
304
|
+
@overload
|
|
305
|
+
def to(self, output: Type[StringVar]) -> ToStringOperation: ...
|
|
306
|
+
|
|
307
|
+
@overload
|
|
308
|
+
def to(self, output: Type[str]) -> ToStringOperation: ...
|
|
309
|
+
|
|
310
|
+
@overload
|
|
311
|
+
def to(self, output: Type[BooleanVar]) -> ToBooleanVarOperation: ...
|
|
312
|
+
|
|
313
|
+
@overload
|
|
314
|
+
def to(
|
|
315
|
+
self, output: Type[NumberVar], var_type: type[int] | type[float] = float
|
|
316
|
+
) -> ToNumberVarOperation: ...
|
|
317
|
+
|
|
318
|
+
@overload
|
|
319
|
+
def to(
|
|
320
|
+
self,
|
|
321
|
+
output: Type[ArrayVar],
|
|
322
|
+
var_type: type[list] | type[tuple] | type[set] = list,
|
|
323
|
+
) -> ToArrayOperation: ...
|
|
324
|
+
|
|
325
|
+
@overload
|
|
326
|
+
def to(
|
|
327
|
+
self, output: Type[ObjectVar], var_type: types.GenericType = dict
|
|
328
|
+
) -> ToObjectOperation: ...
|
|
329
|
+
|
|
330
|
+
@overload
|
|
331
|
+
def to(
|
|
332
|
+
self, output: Type[FunctionVar], var_type: Type[Callable] = Callable
|
|
333
|
+
) -> ToFunctionOperation: ...
|
|
334
|
+
|
|
335
|
+
@overload
|
|
336
|
+
def to(
|
|
337
|
+
self,
|
|
338
|
+
output: Type[OUTPUT] | types.GenericType,
|
|
339
|
+
var_type: types.GenericType | None = None,
|
|
340
|
+
) -> OUTPUT: ...
|
|
341
|
+
|
|
342
|
+
def to(
|
|
343
|
+
self,
|
|
344
|
+
output: Type[OUTPUT] | types.GenericType,
|
|
345
|
+
var_type: types.GenericType | None = None,
|
|
346
|
+
) -> ImmutableVar:
|
|
347
|
+
"""Convert the var to a different type.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
output: The output type.
|
|
351
|
+
var_type: The type of the var.
|
|
352
|
+
|
|
353
|
+
Raises:
|
|
354
|
+
TypeError: If the var_type is not a supported type for the output.
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
The converted var.
|
|
358
|
+
"""
|
|
359
|
+
from .function import FunctionVar, ToFunctionOperation
|
|
360
|
+
from .number import (
|
|
361
|
+
BooleanVar,
|
|
362
|
+
NumberVar,
|
|
363
|
+
ToBooleanVarOperation,
|
|
364
|
+
ToNumberVarOperation,
|
|
365
|
+
)
|
|
366
|
+
from .object import ObjectVar, ToObjectOperation
|
|
367
|
+
from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation
|
|
368
|
+
|
|
369
|
+
base_type = var_type
|
|
370
|
+
if types.is_optional(base_type):
|
|
371
|
+
base_type = types.get_args(base_type)[0]
|
|
372
|
+
|
|
373
|
+
fixed_type = get_origin(base_type) or base_type
|
|
374
|
+
|
|
375
|
+
fixed_output_type = get_origin(output) or output
|
|
376
|
+
|
|
377
|
+
# If the first argument is a python type, we map it to the corresponding Var type.
|
|
378
|
+
if fixed_output_type is dict:
|
|
379
|
+
return self.to(ObjectVar, output)
|
|
380
|
+
if fixed_output_type in (list, tuple, set):
|
|
381
|
+
return self.to(ArrayVar, output)
|
|
382
|
+
if fixed_output_type in (int, float):
|
|
383
|
+
return self.to(NumberVar, output)
|
|
384
|
+
if fixed_output_type is str:
|
|
385
|
+
return self.to(StringVar, output)
|
|
386
|
+
if fixed_output_type is bool:
|
|
387
|
+
return self.to(BooleanVar, output)
|
|
388
|
+
if fixed_output_type is None:
|
|
389
|
+
return ToNoneOperation.create(self)
|
|
390
|
+
|
|
391
|
+
if issubclass(output, BooleanVar):
|
|
392
|
+
return ToBooleanVarOperation.create(self)
|
|
393
|
+
|
|
394
|
+
if issubclass(output, NumberVar):
|
|
395
|
+
if fixed_type is not None:
|
|
396
|
+
if fixed_type is Union:
|
|
397
|
+
inner_types = get_args(base_type)
|
|
398
|
+
if not all(issubclass(t, (int, float)) for t in inner_types):
|
|
399
|
+
raise TypeError(
|
|
400
|
+
f"Unsupported type {var_type} for NumberVar. Must be int or float."
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
elif not issubclass(fixed_type, (int, float)):
|
|
404
|
+
raise TypeError(
|
|
405
|
+
f"Unsupported type {var_type} for NumberVar. Must be int or float."
|
|
406
|
+
)
|
|
407
|
+
return ToNumberVarOperation.create(self, var_type or float)
|
|
408
|
+
|
|
409
|
+
if issubclass(output, ArrayVar):
|
|
410
|
+
if fixed_type is not None and not issubclass(
|
|
411
|
+
fixed_type, (list, tuple, set)
|
|
412
|
+
):
|
|
413
|
+
raise TypeError(
|
|
414
|
+
f"Unsupported type {var_type} for ArrayVar. Must be list, tuple, or set."
|
|
415
|
+
)
|
|
416
|
+
return ToArrayOperation.create(self, var_type or list)
|
|
417
|
+
|
|
418
|
+
if issubclass(output, StringVar):
|
|
419
|
+
return ToStringOperation.create(self, var_type or str)
|
|
420
|
+
|
|
421
|
+
if issubclass(output, (ObjectVar, Base)):
|
|
422
|
+
return ToObjectOperation.create(self, var_type or dict)
|
|
423
|
+
|
|
424
|
+
if issubclass(output, FunctionVar):
|
|
425
|
+
# if fixed_type is not None and not issubclass(fixed_type, Callable):
|
|
426
|
+
# raise TypeError(
|
|
427
|
+
# f"Unsupported type {var_type} for FunctionVar. Must be Callable."
|
|
428
|
+
# )
|
|
429
|
+
return ToFunctionOperation.create(self, var_type or Callable)
|
|
430
|
+
|
|
431
|
+
if issubclass(output, NoneVar):
|
|
432
|
+
return ToNoneOperation.create(self)
|
|
433
|
+
|
|
434
|
+
# If we can't determine the first argument, we just replace the _var_type.
|
|
435
|
+
if not issubclass(output, Var) or var_type is None:
|
|
436
|
+
return dataclasses.replace(
|
|
437
|
+
self,
|
|
438
|
+
_var_type=output,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# We couldn't determine the output type to be any other Var type, so we replace the _var_type.
|
|
442
|
+
if var_type is not None:
|
|
443
|
+
return dataclasses.replace(
|
|
444
|
+
self,
|
|
445
|
+
_var_type=var_type,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
return self
|
|
449
|
+
|
|
450
|
+
def guess_type(self) -> ImmutableVar:
|
|
451
|
+
"""Guesses the type of the variable based on its `_var_type` attribute.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
ImmutableVar: The guessed type of the variable.
|
|
455
|
+
|
|
456
|
+
Raises:
|
|
457
|
+
TypeError: If the type is not supported for guessing.
|
|
458
|
+
"""
|
|
459
|
+
from .number import BooleanVar, NumberVar
|
|
460
|
+
from .object import ObjectVar
|
|
461
|
+
from .sequence import ArrayVar, StringVar
|
|
462
|
+
|
|
463
|
+
var_type = self._var_type
|
|
464
|
+
if var_type is None:
|
|
465
|
+
return self.to(None)
|
|
466
|
+
if types.is_optional(var_type):
|
|
467
|
+
var_type = types.get_args(var_type)[0]
|
|
468
|
+
|
|
469
|
+
if var_type is Any:
|
|
470
|
+
return self
|
|
471
|
+
|
|
472
|
+
fixed_type = get_origin(var_type) or var_type
|
|
473
|
+
|
|
474
|
+
if fixed_type is Union:
|
|
475
|
+
inner_types = get_args(var_type)
|
|
476
|
+
|
|
477
|
+
if all(
|
|
478
|
+
inspect.isclass(t) and issubclass(t, (int, float)) for t in inner_types
|
|
479
|
+
):
|
|
480
|
+
return self.to(NumberVar, self._var_type)
|
|
481
|
+
|
|
482
|
+
if all(inspect.isclass(t) and issubclass(t, Base) for t in inner_types):
|
|
483
|
+
return self.to(ObjectVar, self._var_type)
|
|
484
|
+
|
|
485
|
+
return self
|
|
486
|
+
|
|
487
|
+
if not inspect.isclass(fixed_type):
|
|
488
|
+
raise TypeError(f"Unsupported type {var_type} for guess_type.")
|
|
489
|
+
|
|
490
|
+
if issubclass(fixed_type, bool):
|
|
491
|
+
return self.to(BooleanVar, self._var_type)
|
|
492
|
+
if issubclass(fixed_type, (int, float)):
|
|
493
|
+
return self.to(NumberVar, self._var_type)
|
|
494
|
+
if issubclass(fixed_type, dict):
|
|
495
|
+
return self.to(ObjectVar, self._var_type)
|
|
496
|
+
if issubclass(fixed_type, (list, tuple, set)):
|
|
497
|
+
return self.to(ArrayVar, self._var_type)
|
|
498
|
+
if issubclass(fixed_type, str):
|
|
499
|
+
return self.to(StringVar, self._var_type)
|
|
500
|
+
if issubclass(fixed_type, Base):
|
|
501
|
+
return self.to(ObjectVar, self._var_type)
|
|
502
|
+
return self
|
|
503
|
+
|
|
504
|
+
def get_default_value(self) -> Any:
|
|
505
|
+
"""Get the default value of the var.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
The default value of the var.
|
|
509
|
+
|
|
510
|
+
Raises:
|
|
511
|
+
ImportError: If the var is a dataframe and pandas is not installed.
|
|
512
|
+
"""
|
|
513
|
+
if types.is_optional(self._var_type):
|
|
514
|
+
return None
|
|
515
|
+
|
|
516
|
+
type_ = (
|
|
517
|
+
get_origin(self._var_type)
|
|
518
|
+
if types.is_generic_alias(self._var_type)
|
|
519
|
+
else self._var_type
|
|
520
|
+
)
|
|
521
|
+
if type_ is Literal:
|
|
522
|
+
args = get_args(self._var_type)
|
|
523
|
+
return args[0] if args else None
|
|
524
|
+
if issubclass(type_, str):
|
|
525
|
+
return ""
|
|
526
|
+
if issubclass(type_, types.get_args(Union[int, float])):
|
|
527
|
+
return 0
|
|
528
|
+
if issubclass(type_, bool):
|
|
529
|
+
return False
|
|
530
|
+
if issubclass(type_, list):
|
|
531
|
+
return []
|
|
532
|
+
if issubclass(type_, dict):
|
|
533
|
+
return {}
|
|
534
|
+
if issubclass(type_, tuple):
|
|
535
|
+
return ()
|
|
536
|
+
if types.is_dataframe(type_):
|
|
537
|
+
try:
|
|
538
|
+
import pandas as pd
|
|
539
|
+
|
|
540
|
+
return pd.DataFrame()
|
|
541
|
+
except ImportError as e:
|
|
542
|
+
raise ImportError(
|
|
543
|
+
"Please install pandas to use dataframes in your app."
|
|
544
|
+
) from e
|
|
545
|
+
return set() if issubclass(type_, set) else None
|
|
546
|
+
|
|
547
|
+
def get_setter_name(self, include_state: bool = True) -> str:
|
|
548
|
+
"""Get the name of the var's generated setter function.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
include_state: Whether to include the state name in the setter name.
|
|
552
|
+
|
|
553
|
+
Returns:
|
|
554
|
+
The name of the setter function.
|
|
555
|
+
"""
|
|
556
|
+
var_name_parts = self._var_name.split(".")
|
|
557
|
+
setter = constants.SETTER_PREFIX + var_name_parts[-1]
|
|
558
|
+
var_data = self._get_all_var_data()
|
|
559
|
+
if var_data is None:
|
|
560
|
+
return setter
|
|
561
|
+
if not include_state or var_data.state == "":
|
|
562
|
+
return setter
|
|
563
|
+
return ".".join((var_data.state, setter))
|
|
564
|
+
|
|
565
|
+
def get_setter(self) -> Callable[[BaseState, Any], None]:
|
|
566
|
+
"""Get the var's setter function.
|
|
567
|
+
|
|
568
|
+
Returns:
|
|
569
|
+
A function that that creates a setter for the var.
|
|
570
|
+
"""
|
|
571
|
+
actual_name = self._var_name.split(".")[-1]
|
|
572
|
+
|
|
573
|
+
def setter(state: BaseState, value: Any):
|
|
574
|
+
"""Get the setter for the var.
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
state: The state within which we add the setter function.
|
|
578
|
+
value: The value to set.
|
|
579
|
+
"""
|
|
580
|
+
if self._var_type in [int, float]:
|
|
581
|
+
try:
|
|
582
|
+
value = self._var_type(value)
|
|
583
|
+
setattr(state, actual_name, value)
|
|
584
|
+
except ValueError:
|
|
585
|
+
console.debug(
|
|
586
|
+
f"{type(state).__name__}.{self._var_name}: Failed conversion of {value} to '{self._var_type.__name__}'. Value not set.",
|
|
587
|
+
)
|
|
588
|
+
else:
|
|
589
|
+
setattr(state, actual_name, value)
|
|
590
|
+
|
|
591
|
+
setter.__qualname__ = self.get_setter_name()
|
|
592
|
+
|
|
593
|
+
return setter
|
|
594
|
+
|
|
595
|
+
def _var_set_state(self, state: type[BaseState] | str):
|
|
596
|
+
"""Set the state of the var.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
state: The state to set.
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
The var with the state set.
|
|
603
|
+
"""
|
|
604
|
+
formatted_state_name = (
|
|
605
|
+
state
|
|
606
|
+
if isinstance(state, str)
|
|
607
|
+
else format_state_name(state.get_full_name())
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
return StateOperation.create(
|
|
611
|
+
formatted_state_name,
|
|
612
|
+
self,
|
|
613
|
+
_var_data=VarData.merge(VarData.from_state(state), self._var_data),
|
|
614
|
+
).guess_type()
|
|
615
|
+
|
|
616
|
+
def __eq__(self, other: ImmutableVar | Any) -> BooleanVar:
|
|
617
|
+
"""Check if the current variable is equal to the given variable.
|
|
618
|
+
|
|
619
|
+
Args:
|
|
620
|
+
other (ImmutableVar | Any): The variable to compare with.
|
|
621
|
+
|
|
622
|
+
Returns:
|
|
623
|
+
BooleanVar: A BooleanVar object representing the result of the equality check.
|
|
624
|
+
"""
|
|
625
|
+
from .number import equal_operation
|
|
626
|
+
|
|
627
|
+
return equal_operation(self, other)
|
|
628
|
+
|
|
629
|
+
def __ne__(self, other: ImmutableVar | Any) -> BooleanVar:
|
|
630
|
+
"""Check if the current object is not equal to the given object.
|
|
631
|
+
|
|
632
|
+
Parameters:
|
|
633
|
+
other (ImmutableVar | Any): The object to compare with.
|
|
634
|
+
|
|
635
|
+
Returns:
|
|
636
|
+
BooleanVar: A BooleanVar object representing the result of the comparison.
|
|
637
|
+
"""
|
|
638
|
+
from .number import equal_operation
|
|
639
|
+
|
|
640
|
+
return ~equal_operation(self, other)
|
|
641
|
+
|
|
642
|
+
def bool(self) -> BooleanVar:
|
|
643
|
+
"""Convert the var to a boolean.
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
The boolean var.
|
|
647
|
+
"""
|
|
648
|
+
from .number import boolify
|
|
649
|
+
|
|
650
|
+
return boolify(self)
|
|
651
|
+
|
|
652
|
+
def __and__(self, other: ImmutableVar | Any) -> ImmutableVar:
|
|
653
|
+
"""Perform a logical AND operation on the current instance and another variable.
|
|
654
|
+
|
|
655
|
+
Args:
|
|
656
|
+
other: The variable to perform the logical AND operation with.
|
|
657
|
+
|
|
658
|
+
Returns:
|
|
659
|
+
A `BooleanVar` object representing the result of the logical AND operation.
|
|
660
|
+
"""
|
|
661
|
+
return and_operation(self, other)
|
|
662
|
+
|
|
663
|
+
def __rand__(self, other: ImmutableVar | Any) -> ImmutableVar:
|
|
664
|
+
"""Perform a logical AND operation on the current instance and another variable.
|
|
665
|
+
|
|
666
|
+
Args:
|
|
667
|
+
other: The variable to perform the logical AND operation with.
|
|
668
|
+
|
|
669
|
+
Returns:
|
|
670
|
+
A `BooleanVar` object representing the result of the logical AND operation.
|
|
671
|
+
"""
|
|
672
|
+
return and_operation(other, self)
|
|
673
|
+
|
|
674
|
+
def __or__(self, other: ImmutableVar | Any) -> ImmutableVar:
|
|
675
|
+
"""Perform a logical OR operation on the current instance and another variable.
|
|
676
|
+
|
|
677
|
+
Args:
|
|
678
|
+
other: The variable to perform the logical OR operation with.
|
|
679
|
+
|
|
680
|
+
Returns:
|
|
681
|
+
A `BooleanVar` object representing the result of the logical OR operation.
|
|
682
|
+
"""
|
|
683
|
+
return or_operation(self, other)
|
|
684
|
+
|
|
685
|
+
def __ror__(self, other: ImmutableVar | Any) -> ImmutableVar:
|
|
686
|
+
"""Perform a logical OR operation on the current instance and another variable.
|
|
687
|
+
|
|
688
|
+
Args:
|
|
689
|
+
other: The variable to perform the logical OR operation with.
|
|
690
|
+
|
|
691
|
+
Returns:
|
|
692
|
+
A `BooleanVar` object representing the result of the logical OR operation.
|
|
693
|
+
"""
|
|
694
|
+
return or_operation(other, self)
|
|
695
|
+
|
|
696
|
+
def __invert__(self) -> BooleanVar:
|
|
697
|
+
"""Perform a logical NOT operation on the current instance.
|
|
698
|
+
|
|
699
|
+
Returns:
|
|
700
|
+
A `BooleanVar` object representing the result of the logical NOT operation.
|
|
701
|
+
"""
|
|
702
|
+
return ~self.bool()
|
|
703
|
+
|
|
704
|
+
def to_string(self):
|
|
705
|
+
"""Convert the var to a string.
|
|
706
|
+
|
|
707
|
+
Returns:
|
|
708
|
+
The string var.
|
|
709
|
+
"""
|
|
710
|
+
from .function import JSON_STRINGIFY
|
|
711
|
+
from .sequence import StringVar
|
|
712
|
+
|
|
713
|
+
return JSON_STRINGIFY.call(self).to(StringVar)
|
|
714
|
+
|
|
715
|
+
def as_ref(self) -> ImmutableVar:
|
|
716
|
+
"""Get a reference to the var.
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
The reference to the var.
|
|
720
|
+
"""
|
|
721
|
+
from .object import ObjectVar
|
|
722
|
+
|
|
723
|
+
refs = ImmutableVar(
|
|
724
|
+
_var_name="refs",
|
|
725
|
+
_var_data=VarData(
|
|
726
|
+
imports={
|
|
727
|
+
f"/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")]
|
|
728
|
+
}
|
|
729
|
+
),
|
|
730
|
+
).to(ObjectVar)
|
|
731
|
+
return refs[LiteralVar.create(str(self))]
|
|
732
|
+
|
|
733
|
+
@deprecated("Use `.js_type()` instead.")
|
|
734
|
+
def _type(self) -> StringVar:
|
|
735
|
+
"""Returns the type of the object.
|
|
736
|
+
|
|
737
|
+
This method uses the `typeof` function from the `FunctionStringVar` class
|
|
738
|
+
to determine the type of the object.
|
|
739
|
+
|
|
740
|
+
Returns:
|
|
741
|
+
StringVar: A string variable representing the type of the object.
|
|
742
|
+
"""
|
|
743
|
+
return self.js_type()
|
|
744
|
+
|
|
745
|
+
def js_type(self) -> StringVar:
|
|
746
|
+
"""Returns the javascript type of the object.
|
|
747
|
+
|
|
748
|
+
This method uses the `typeof` function from the `FunctionStringVar` class
|
|
749
|
+
to determine the type of the object.
|
|
750
|
+
|
|
751
|
+
Returns:
|
|
752
|
+
StringVar: A string variable representing the type of the object.
|
|
753
|
+
"""
|
|
754
|
+
from .function import FunctionStringVar
|
|
755
|
+
from .sequence import StringVar
|
|
756
|
+
|
|
757
|
+
type_of = FunctionStringVar("typeof")
|
|
758
|
+
return type_of.call(self).to(StringVar)
|
|
759
|
+
|
|
760
|
+
def without_data(self):
|
|
761
|
+
"""Create a copy of the var without the data.
|
|
762
|
+
|
|
763
|
+
Returns:
|
|
764
|
+
The var without the data.
|
|
765
|
+
"""
|
|
766
|
+
return dataclasses.replace(self, _var_data=None)
|
|
767
|
+
|
|
768
|
+
def contains(self, value: Any = None, field: Any = None):
|
|
769
|
+
"""Get an attribute of the var.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
value: The value to check for.
|
|
773
|
+
field: The field to check for.
|
|
774
|
+
|
|
775
|
+
Raises:
|
|
776
|
+
TypeError: If the var does not support contains check.
|
|
777
|
+
"""
|
|
778
|
+
raise TypeError(
|
|
779
|
+
f"Var of type {self._var_type} does not support contains check."
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
def __get__(self, instance: Any, owner: Any):
|
|
783
|
+
"""Get the var.
|
|
784
|
+
|
|
785
|
+
Args:
|
|
786
|
+
instance: The instance to get the var from.
|
|
787
|
+
owner: The owner of the var.
|
|
788
|
+
|
|
789
|
+
Returns:
|
|
790
|
+
The var.
|
|
791
|
+
"""
|
|
792
|
+
return self
|
|
793
|
+
|
|
794
|
+
def reverse(self):
|
|
795
|
+
"""Reverse the var.
|
|
796
|
+
|
|
797
|
+
Raises:
|
|
798
|
+
TypeError: If the var does not support reverse.
|
|
799
|
+
"""
|
|
800
|
+
raise TypeError("Cannot reverse non-list var.")
|
|
801
|
+
|
|
802
|
+
def __getattr__(self, name: str):
|
|
803
|
+
"""Get an attribute of the var.
|
|
804
|
+
|
|
805
|
+
Args:
|
|
806
|
+
name: The name of the attribute.
|
|
807
|
+
|
|
808
|
+
Returns:
|
|
809
|
+
The attribute.
|
|
810
|
+
|
|
811
|
+
Raises:
|
|
812
|
+
VarAttributeError: If the attribute does not exist.
|
|
813
|
+
TypeError: If the var type is Any.
|
|
814
|
+
"""
|
|
815
|
+
if name.startswith("_"):
|
|
816
|
+
return super(ImmutableVar, self).__getattribute__(name)
|
|
817
|
+
if self._var_type is Any:
|
|
818
|
+
raise TypeError(
|
|
819
|
+
f"You must provide an annotation for the state var `{str(self)}`. Annotation cannot be `{self._var_type}`."
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
if name in REPLACED_NAMES:
|
|
823
|
+
raise VarAttributeError(
|
|
824
|
+
f"Field {name!r} was renamed to {REPLACED_NAMES[name]!r}"
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
raise VarAttributeError(
|
|
828
|
+
f"The State var has no attribute '{name}' or may have been annotated wrongly.",
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
def _decode(self) -> Any:
|
|
832
|
+
"""Decode Var as a python value.
|
|
833
|
+
|
|
834
|
+
Note that Var with state set cannot be decoded python-side and will be
|
|
835
|
+
returned as full_name.
|
|
836
|
+
|
|
837
|
+
Returns:
|
|
838
|
+
The decoded value or the Var name.
|
|
839
|
+
"""
|
|
840
|
+
if isinstance(self, LiteralVar):
|
|
841
|
+
return self._var_value # type: ignore
|
|
842
|
+
try:
|
|
843
|
+
return json.loads(str(self))
|
|
844
|
+
except ValueError:
|
|
845
|
+
try:
|
|
846
|
+
return json.loads(self.json())
|
|
847
|
+
except (ValueError, NotImplementedError):
|
|
848
|
+
return str(self)
|
|
849
|
+
|
|
850
|
+
@property
|
|
851
|
+
def _var_state(self) -> str:
|
|
852
|
+
"""Compat method for getting the state.
|
|
853
|
+
|
|
854
|
+
Returns:
|
|
855
|
+
The state name associated with the var.
|
|
856
|
+
"""
|
|
857
|
+
var_data = self._get_all_var_data()
|
|
858
|
+
return var_data.state if var_data else ""
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
OUTPUT = TypeVar("OUTPUT", bound=ImmutableVar)
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
def _encode_var(value: ImmutableVar) -> str:
|
|
865
|
+
"""Encode the state name into a formatted var.
|
|
866
|
+
|
|
867
|
+
Args:
|
|
868
|
+
value: The value to encode the state name into.
|
|
869
|
+
|
|
870
|
+
Returns:
|
|
871
|
+
The encoded var.
|
|
872
|
+
"""
|
|
873
|
+
return f"{value}"
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
serializers.serializer(_encode_var)
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
class LiteralVar(ImmutableVar):
|
|
880
|
+
"""Base class for immutable literal vars."""
|
|
881
|
+
|
|
882
|
+
@classmethod
|
|
883
|
+
def create(
|
|
884
|
+
cls,
|
|
885
|
+
value: Any,
|
|
886
|
+
_var_data: VarData | None = None,
|
|
887
|
+
) -> ImmutableVar:
|
|
888
|
+
"""Create a var from a value.
|
|
889
|
+
|
|
890
|
+
Args:
|
|
891
|
+
value: The value to create the var from.
|
|
892
|
+
_var_data: Additional hooks and imports associated with the Var.
|
|
893
|
+
|
|
894
|
+
Returns:
|
|
895
|
+
The var.
|
|
896
|
+
|
|
897
|
+
Raises:
|
|
898
|
+
TypeError: If the value is not a supported type for LiteralVar.
|
|
899
|
+
"""
|
|
900
|
+
from .number import LiteralBooleanVar, LiteralNumberVar
|
|
901
|
+
from .object import LiteralObjectVar
|
|
902
|
+
from .sequence import LiteralArrayVar, LiteralStringVar
|
|
903
|
+
|
|
904
|
+
if isinstance(value, ImmutableVar):
|
|
905
|
+
if _var_data is None:
|
|
906
|
+
return value
|
|
907
|
+
return value._replace(merge_var_data=_var_data)
|
|
908
|
+
|
|
909
|
+
if isinstance(value, str):
|
|
910
|
+
return LiteralStringVar.create(value, _var_data=_var_data)
|
|
911
|
+
|
|
912
|
+
if isinstance(value, bool):
|
|
913
|
+
return LiteralBooleanVar.create(value, _var_data=_var_data)
|
|
914
|
+
|
|
915
|
+
if isinstance(value, (int, float)):
|
|
916
|
+
return LiteralNumberVar.create(value, _var_data=_var_data)
|
|
917
|
+
|
|
918
|
+
if isinstance(value, dict):
|
|
919
|
+
return LiteralObjectVar.create(value, _var_data=_var_data)
|
|
920
|
+
|
|
921
|
+
if isinstance(value, (list, tuple, set)):
|
|
922
|
+
return LiteralArrayVar.create(value, _var_data=_var_data)
|
|
923
|
+
|
|
924
|
+
if value is None:
|
|
925
|
+
return LiteralNoneVar.create(_var_data=_var_data)
|
|
926
|
+
|
|
927
|
+
from reflex.event import EventChain, EventSpec
|
|
928
|
+
from reflex.utils.format import get_event_handler_parts
|
|
929
|
+
|
|
930
|
+
from .function import ArgsFunctionOperation, FunctionStringVar
|
|
931
|
+
from .object import LiteralObjectVar
|
|
932
|
+
|
|
933
|
+
if isinstance(value, EventSpec):
|
|
934
|
+
event_name = LiteralVar.create(
|
|
935
|
+
".".join(filter(None, get_event_handler_parts(value.handler)))
|
|
936
|
+
)
|
|
937
|
+
event_args = LiteralVar.create(
|
|
938
|
+
{str(name): value for name, value in value.args}
|
|
939
|
+
)
|
|
940
|
+
event_client_name = LiteralVar.create(value.client_handler_name)
|
|
941
|
+
return FunctionStringVar("Event").call(
|
|
942
|
+
event_name,
|
|
943
|
+
event_args,
|
|
944
|
+
*([event_client_name] if value.client_handler_name else []),
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
if isinstance(value, EventChain):
|
|
948
|
+
sig = inspect.signature(value.args_spec) # type: ignore
|
|
949
|
+
if sig.parameters:
|
|
950
|
+
arg_def = tuple((f"_{p}" for p in sig.parameters))
|
|
951
|
+
arg_def_expr = LiteralVar.create(
|
|
952
|
+
[ImmutableVar.create_safe(arg) for arg in arg_def]
|
|
953
|
+
)
|
|
954
|
+
else:
|
|
955
|
+
# add a default argument for addEvents if none were specified in value.args_spec
|
|
956
|
+
# used to trigger the preventDefault() on the event.
|
|
957
|
+
arg_def = ("...args",)
|
|
958
|
+
arg_def_expr = ImmutableVar.create_safe("args")
|
|
959
|
+
|
|
960
|
+
return ArgsFunctionOperation.create(
|
|
961
|
+
arg_def,
|
|
962
|
+
FunctionStringVar.create("addEvents").call(
|
|
963
|
+
LiteralVar.create(
|
|
964
|
+
[LiteralVar.create(event) for event in value.events]
|
|
965
|
+
),
|
|
966
|
+
arg_def_expr,
|
|
967
|
+
LiteralVar.create(value.event_actions),
|
|
968
|
+
),
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
if isinstance(value, Base):
|
|
972
|
+
return LiteralObjectVar.create(
|
|
973
|
+
{k: (None if callable(v) else v) for k, v in value.dict().items()},
|
|
974
|
+
_var_type=type(value),
|
|
975
|
+
_var_data=_var_data,
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
serialized_value = serializers.serialize(value)
|
|
979
|
+
if serialized_value is not None:
|
|
980
|
+
if isinstance(serialized_value, dict):
|
|
981
|
+
return LiteralObjectVar.create(
|
|
982
|
+
serialized_value,
|
|
983
|
+
_var_type=type(value),
|
|
984
|
+
_var_data=_var_data,
|
|
985
|
+
)
|
|
986
|
+
return LiteralVar.create(serialized_value, _var_data=_var_data)
|
|
987
|
+
|
|
988
|
+
raise TypeError(
|
|
989
|
+
f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}."
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
def __post_init__(self):
|
|
993
|
+
"""Post-initialize the var."""
|
|
994
|
+
|
|
995
|
+
def json(self) -> str:
|
|
996
|
+
"""Serialize the var to a JSON string.
|
|
997
|
+
|
|
998
|
+
Raises:
|
|
999
|
+
NotImplementedError: If the method is not implemented.
|
|
1000
|
+
"""
|
|
1001
|
+
raise NotImplementedError(
|
|
1002
|
+
"LiteralVar subclasses must implement the json method."
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
P = ParamSpec("P")
|
|
1007
|
+
T = TypeVar("T")
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
# NoReturn is used to match CustomVarOperationReturn with no type hint.
|
|
1011
|
+
@overload
|
|
1012
|
+
def var_operation(
|
|
1013
|
+
func: Callable[P, CustomVarOperationReturn[NoReturn]],
|
|
1014
|
+
) -> Callable[P, ImmutableVar]: ...
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
@overload
|
|
1018
|
+
def var_operation(
|
|
1019
|
+
func: Callable[P, CustomVarOperationReturn[bool]],
|
|
1020
|
+
) -> Callable[P, BooleanVar]: ...
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
NUMBER_T = TypeVar("NUMBER_T", int, float, Union[int, float])
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
@overload
|
|
1027
|
+
def var_operation(
|
|
1028
|
+
func: Callable[P, CustomVarOperationReturn[NUMBER_T]],
|
|
1029
|
+
) -> Callable[P, NumberVar[NUMBER_T]]: ...
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
@overload
|
|
1033
|
+
def var_operation(
|
|
1034
|
+
func: Callable[P, CustomVarOperationReturn[str]],
|
|
1035
|
+
) -> Callable[P, StringVar]: ...
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
LIST_T = TypeVar("LIST_T", bound=Union[List[Any], Tuple, Set])
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
@overload
|
|
1042
|
+
def var_operation(
|
|
1043
|
+
func: Callable[P, CustomVarOperationReturn[LIST_T]],
|
|
1044
|
+
) -> Callable[P, ArrayVar[LIST_T]]: ...
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Dict)
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
@overload
|
|
1051
|
+
def var_operation(
|
|
1052
|
+
func: Callable[P, CustomVarOperationReturn[OBJECT_TYPE]],
|
|
1053
|
+
) -> Callable[P, ObjectVar[OBJECT_TYPE]]: ...
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
def var_operation(
|
|
1057
|
+
func: Callable[P, CustomVarOperationReturn[T]],
|
|
1058
|
+
) -> Callable[P, ImmutableVar[T]]:
|
|
1059
|
+
"""Decorator for creating a var operation.
|
|
1060
|
+
|
|
1061
|
+
Example:
|
|
1062
|
+
```python
|
|
1063
|
+
@var_operation
|
|
1064
|
+
def add(a: NumberVar, b: NumberVar):
|
|
1065
|
+
return custom_var_operation(f"{a} + {b}")
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
Args:
|
|
1069
|
+
func: The function to decorate.
|
|
1070
|
+
|
|
1071
|
+
Returns:
|
|
1072
|
+
The decorated function.
|
|
1073
|
+
"""
|
|
1074
|
+
|
|
1075
|
+
@functools.wraps(func)
|
|
1076
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> ImmutableVar[T]:
|
|
1077
|
+
func_args = list(inspect.signature(func).parameters)
|
|
1078
|
+
args_vars = {
|
|
1079
|
+
func_args[i]: (
|
|
1080
|
+
LiteralVar.create(arg) if not isinstance(arg, ImmutableVar) else arg
|
|
1081
|
+
)
|
|
1082
|
+
for i, arg in enumerate(args)
|
|
1083
|
+
}
|
|
1084
|
+
kwargs_vars = {
|
|
1085
|
+
key: LiteralVar.create(value)
|
|
1086
|
+
if not isinstance(value, ImmutableVar)
|
|
1087
|
+
else value
|
|
1088
|
+
for key, value in kwargs.items()
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
return CustomVarOperation.create(
|
|
1092
|
+
args=tuple(list(args_vars.items()) + list(kwargs_vars.items())),
|
|
1093
|
+
return_var=func(*args_vars.values(), **kwargs_vars), # type: ignore
|
|
1094
|
+
).guess_type()
|
|
1095
|
+
|
|
1096
|
+
return wrapper
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
def unionize(*args: Type) -> Type:
|
|
1100
|
+
"""Unionize the types.
|
|
1101
|
+
|
|
1102
|
+
Args:
|
|
1103
|
+
args: The types to unionize.
|
|
1104
|
+
|
|
1105
|
+
Returns:
|
|
1106
|
+
The unionized types.
|
|
1107
|
+
"""
|
|
1108
|
+
if not args:
|
|
1109
|
+
return Any
|
|
1110
|
+
first, *rest = args
|
|
1111
|
+
if not rest:
|
|
1112
|
+
return first
|
|
1113
|
+
return Union[first, unionize(*rest)]
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
def figure_out_type(value: Any) -> types.GenericType:
|
|
1117
|
+
"""Figure out the type of the value.
|
|
1118
|
+
|
|
1119
|
+
Args:
|
|
1120
|
+
value: The value to figure out the type of.
|
|
1121
|
+
|
|
1122
|
+
Returns:
|
|
1123
|
+
The type of the value.
|
|
1124
|
+
"""
|
|
1125
|
+
if isinstance(value, list):
|
|
1126
|
+
return List[unionize(*(figure_out_type(v) for v in value))]
|
|
1127
|
+
if isinstance(value, set):
|
|
1128
|
+
return Set[unionize(*(figure_out_type(v) for v in value))]
|
|
1129
|
+
if isinstance(value, tuple):
|
|
1130
|
+
return Tuple[unionize(*(figure_out_type(v) for v in value)), ...]
|
|
1131
|
+
if isinstance(value, dict):
|
|
1132
|
+
return Dict[
|
|
1133
|
+
unionize(*(figure_out_type(k) for k in value)),
|
|
1134
|
+
unionize(*(figure_out_type(v) for v in value.values())),
|
|
1135
|
+
]
|
|
1136
|
+
if isinstance(value, ImmutableVar):
|
|
1137
|
+
return value._var_type
|
|
1138
|
+
return type(value)
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
class cached_property_no_lock(functools.cached_property):
|
|
1142
|
+
"""A special version of functools.cached_property that does not use a lock."""
|
|
1143
|
+
|
|
1144
|
+
def __init__(self, func):
|
|
1145
|
+
"""Initialize the cached_property_no_lock.
|
|
1146
|
+
|
|
1147
|
+
Args:
|
|
1148
|
+
func: The function to cache.
|
|
1149
|
+
"""
|
|
1150
|
+
super().__init__(func)
|
|
1151
|
+
self.lock = contextlib.nullcontext()
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
class CachedVarOperation:
|
|
1155
|
+
"""Base class for cached var operations to lower boilerplate code."""
|
|
1156
|
+
|
|
1157
|
+
def __post_init__(self):
|
|
1158
|
+
"""Post-initialize the CachedVarOperation."""
|
|
1159
|
+
object.__delattr__(self, "_var_name")
|
|
1160
|
+
|
|
1161
|
+
def __getattr__(self, name: str) -> Any:
|
|
1162
|
+
"""Get an attribute of the var.
|
|
1163
|
+
|
|
1164
|
+
Args:
|
|
1165
|
+
name: The name of the attribute.
|
|
1166
|
+
|
|
1167
|
+
Returns:
|
|
1168
|
+
The attribute.
|
|
1169
|
+
"""
|
|
1170
|
+
if name == "_var_name":
|
|
1171
|
+
return self._cached_var_name
|
|
1172
|
+
|
|
1173
|
+
parent_classes = inspect.getmro(self.__class__)
|
|
1174
|
+
|
|
1175
|
+
next_class = parent_classes[parent_classes.index(CachedVarOperation) + 1]
|
|
1176
|
+
|
|
1177
|
+
return next_class.__getattr__(self, name) # type: ignore
|
|
1178
|
+
|
|
1179
|
+
def _get_all_var_data(self) -> VarData | None:
|
|
1180
|
+
"""Get all VarData associated with the Var.
|
|
1181
|
+
|
|
1182
|
+
Returns:
|
|
1183
|
+
The VarData of the components and all of its children.
|
|
1184
|
+
"""
|
|
1185
|
+
return self._cached_get_all_var_data
|
|
1186
|
+
|
|
1187
|
+
@cached_property_no_lock
|
|
1188
|
+
def _cached_get_all_var_data(self) -> VarData | None:
|
|
1189
|
+
"""Get the cached VarData.
|
|
1190
|
+
|
|
1191
|
+
Returns:
|
|
1192
|
+
The cached VarData.
|
|
1193
|
+
"""
|
|
1194
|
+
return VarData.merge(
|
|
1195
|
+
*map(
|
|
1196
|
+
lambda value: (
|
|
1197
|
+
value._get_all_var_data()
|
|
1198
|
+
if isinstance(value, ImmutableVar)
|
|
1199
|
+
else None
|
|
1200
|
+
),
|
|
1201
|
+
map(
|
|
1202
|
+
lambda field: getattr(self, field.name),
|
|
1203
|
+
dataclasses.fields(self), # type: ignore
|
|
1204
|
+
),
|
|
1205
|
+
),
|
|
1206
|
+
self._var_data,
|
|
1207
|
+
)
|
|
1208
|
+
|
|
1209
|
+
def __hash__(self) -> int:
|
|
1210
|
+
"""Calculate the hash of the object.
|
|
1211
|
+
|
|
1212
|
+
Returns:
|
|
1213
|
+
The hash of the object.
|
|
1214
|
+
"""
|
|
1215
|
+
return hash(
|
|
1216
|
+
(
|
|
1217
|
+
self.__class__.__name__,
|
|
1218
|
+
*[
|
|
1219
|
+
getattr(self, field.name)
|
|
1220
|
+
for field in dataclasses.fields(self) # type: ignore
|
|
1221
|
+
if field.name not in ["_var_name", "_var_data", "_var_type"]
|
|
1222
|
+
],
|
|
1223
|
+
)
|
|
1224
|
+
)
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
def and_operation(a: ImmutableVar | Any, b: ImmutableVar | Any) -> ImmutableVar:
|
|
1228
|
+
"""Perform a logical AND operation on two variables.
|
|
1229
|
+
|
|
1230
|
+
Args:
|
|
1231
|
+
a: The first variable.
|
|
1232
|
+
b: The second variable.
|
|
1233
|
+
|
|
1234
|
+
Returns:
|
|
1235
|
+
The result of the logical AND operation.
|
|
1236
|
+
"""
|
|
1237
|
+
return _and_operation(a, b) # type: ignore
|
|
1238
|
+
|
|
1239
|
+
|
|
1240
|
+
@var_operation
|
|
1241
|
+
def _and_operation(a: ImmutableVar, b: ImmutableVar):
|
|
1242
|
+
"""Perform a logical AND operation on two variables.
|
|
1243
|
+
|
|
1244
|
+
Args:
|
|
1245
|
+
a: The first variable.
|
|
1246
|
+
b: The second variable.
|
|
1247
|
+
|
|
1248
|
+
Returns:
|
|
1249
|
+
The result of the logical AND operation.
|
|
1250
|
+
"""
|
|
1251
|
+
return var_operation_return(
|
|
1252
|
+
js_expression=f"({a} && {b})",
|
|
1253
|
+
var_type=unionize(a._var_type, b._var_type),
|
|
1254
|
+
)
|
|
1255
|
+
|
|
1256
|
+
|
|
1257
|
+
def or_operation(a: ImmutableVar | Any, b: ImmutableVar | Any) -> ImmutableVar:
|
|
1258
|
+
"""Perform a logical OR operation on two variables.
|
|
1259
|
+
|
|
1260
|
+
Args:
|
|
1261
|
+
a: The first variable.
|
|
1262
|
+
b: The second variable.
|
|
1263
|
+
|
|
1264
|
+
Returns:
|
|
1265
|
+
The result of the logical OR operation.
|
|
1266
|
+
"""
|
|
1267
|
+
return _or_operation(a, b) # type: ignore
|
|
1268
|
+
|
|
1269
|
+
|
|
1270
|
+
@var_operation
|
|
1271
|
+
def _or_operation(a: ImmutableVar, b: ImmutableVar):
|
|
1272
|
+
"""Perform a logical OR operation on two variables.
|
|
1273
|
+
|
|
1274
|
+
Args:
|
|
1275
|
+
a: The first variable.
|
|
1276
|
+
b: The second variable.
|
|
1277
|
+
|
|
1278
|
+
Returns:
|
|
1279
|
+
The result of the logical OR operation.
|
|
1280
|
+
"""
|
|
1281
|
+
return var_operation_return(
|
|
1282
|
+
js_expression=f"({a} || {b})",
|
|
1283
|
+
var_type=unionize(a._var_type, b._var_type),
|
|
1284
|
+
)
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
@dataclasses.dataclass(
|
|
1288
|
+
eq=False,
|
|
1289
|
+
frozen=True,
|
|
1290
|
+
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
|
1291
|
+
)
|
|
1292
|
+
class ImmutableCallableVar(ImmutableVar):
|
|
1293
|
+
"""Decorate a Var-returning function to act as both a Var and a function.
|
|
1294
|
+
|
|
1295
|
+
This is used as a compatibility shim for replacing Var objects in the
|
|
1296
|
+
API with functions that return a family of Var.
|
|
1297
|
+
"""
|
|
1298
|
+
|
|
1299
|
+
fn: Callable[..., ImmutableVar] = dataclasses.field(
|
|
1300
|
+
default_factory=lambda: lambda: ImmutableVar(_var_name="undefined")
|
|
1301
|
+
)
|
|
1302
|
+
original_var: ImmutableVar = dataclasses.field(
|
|
1303
|
+
default_factory=lambda: ImmutableVar(_var_name="undefined")
|
|
1304
|
+
)
|
|
1305
|
+
|
|
1306
|
+
def __init__(self, fn: Callable[..., ImmutableVar]):
|
|
1307
|
+
"""Initialize a CallableVar.
|
|
1308
|
+
|
|
1309
|
+
Args:
|
|
1310
|
+
fn: The function to decorate (must return Var)
|
|
1311
|
+
"""
|
|
1312
|
+
original_var = fn()
|
|
1313
|
+
super(ImmutableCallableVar, self).__init__(
|
|
1314
|
+
_var_name=original_var._var_name,
|
|
1315
|
+
_var_type=original_var._var_type,
|
|
1316
|
+
_var_data=VarData.merge(original_var._get_all_var_data()),
|
|
1317
|
+
)
|
|
1318
|
+
object.__setattr__(self, "fn", fn)
|
|
1319
|
+
object.__setattr__(self, "original_var", original_var)
|
|
1320
|
+
|
|
1321
|
+
def __call__(self, *args, **kwargs) -> ImmutableVar:
|
|
1322
|
+
"""Call the decorated function.
|
|
1323
|
+
|
|
1324
|
+
Args:
|
|
1325
|
+
*args: The args to pass to the function.
|
|
1326
|
+
**kwargs: The kwargs to pass to the function.
|
|
1327
|
+
|
|
1328
|
+
Returns:
|
|
1329
|
+
The Var returned from calling the function.
|
|
1330
|
+
"""
|
|
1331
|
+
return self.fn(*args, **kwargs)
|
|
1332
|
+
|
|
1333
|
+
def __hash__(self) -> int:
|
|
1334
|
+
"""Calculate the hash of the object.
|
|
1335
|
+
|
|
1336
|
+
Returns:
|
|
1337
|
+
The hash of the object.
|
|
1338
|
+
"""
|
|
1339
|
+
return hash((self.__class__.__name__, self.original_var))
|
|
1340
|
+
|
|
1341
|
+
|
|
1342
|
+
RETURN_TYPE = TypeVar("RETURN_TYPE")
|
|
1343
|
+
|
|
1344
|
+
DICT_KEY = TypeVar("DICT_KEY")
|
|
1345
|
+
DICT_VAL = TypeVar("DICT_VAL")
|
|
1346
|
+
|
|
1347
|
+
LIST_INSIDE = TypeVar("LIST_INSIDE")
|
|
1348
|
+
|
|
1349
|
+
|
|
1350
|
+
class FakeComputedVarBaseClass(Var, property):
|
|
1351
|
+
"""A fake base class for ComputedVar to avoid inheriting from property."""
|
|
1352
|
+
|
|
1353
|
+
__pydantic_run_validation__ = False
|
|
1354
|
+
|
|
1355
|
+
|
|
1356
|
+
def is_computed_var(obj: Any) -> TypeGuard[ImmutableComputedVar]:
|
|
1357
|
+
"""Check if the object is a ComputedVar.
|
|
1358
|
+
|
|
1359
|
+
Args:
|
|
1360
|
+
obj: The object to check.
|
|
1361
|
+
|
|
1362
|
+
Returns:
|
|
1363
|
+
Whether the object is a ComputedVar.
|
|
1364
|
+
"""
|
|
1365
|
+
return isinstance(obj, FakeComputedVarBaseClass)
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
@dataclasses.dataclass(
|
|
1369
|
+
eq=False,
|
|
1370
|
+
frozen=True,
|
|
1371
|
+
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
|
1372
|
+
)
|
|
1373
|
+
class ImmutableComputedVar(ImmutableVar[RETURN_TYPE]):
|
|
1374
|
+
"""A field with computed getters."""
|
|
1375
|
+
|
|
1376
|
+
# Whether to track dependencies and cache computed values
|
|
1377
|
+
_cache: bool = dataclasses.field(default=False)
|
|
1378
|
+
|
|
1379
|
+
# Whether the computed var is a backend var
|
|
1380
|
+
_backend: bool = dataclasses.field(default=False)
|
|
1381
|
+
|
|
1382
|
+
# The initial value of the computed var
|
|
1383
|
+
_initial_value: RETURN_TYPE | types.Unset = dataclasses.field(default=types.Unset())
|
|
1384
|
+
|
|
1385
|
+
# Explicit var dependencies to track
|
|
1386
|
+
_static_deps: set[str] = dataclasses.field(default_factory=set)
|
|
1387
|
+
|
|
1388
|
+
# Whether var dependencies should be auto-determined
|
|
1389
|
+
_auto_deps: bool = dataclasses.field(default=True)
|
|
1390
|
+
|
|
1391
|
+
# Interval at which the computed var should be updated
|
|
1392
|
+
_update_interval: Optional[datetime.timedelta] = dataclasses.field(default=None)
|
|
1393
|
+
|
|
1394
|
+
_fget: Callable[[BaseState], RETURN_TYPE] = dataclasses.field(
|
|
1395
|
+
default_factory=lambda: lambda _: None
|
|
1396
|
+
) # type: ignore
|
|
1397
|
+
|
|
1398
|
+
def __init__(
|
|
1399
|
+
self,
|
|
1400
|
+
fget: Callable[[BASE_STATE], RETURN_TYPE],
|
|
1401
|
+
initial_value: RETURN_TYPE | types.Unset = types.Unset(),
|
|
1402
|
+
cache: bool = False,
|
|
1403
|
+
deps: Optional[List[Union[str, ImmutableVar]]] = None,
|
|
1404
|
+
auto_deps: bool = True,
|
|
1405
|
+
interval: Optional[Union[int, datetime.timedelta]] = None,
|
|
1406
|
+
backend: bool | None = None,
|
|
1407
|
+
**kwargs,
|
|
1408
|
+
):
|
|
1409
|
+
"""Initialize a ComputedVar.
|
|
1410
|
+
|
|
1411
|
+
Args:
|
|
1412
|
+
fget: The getter function.
|
|
1413
|
+
initial_value: The initial value of the computed var.
|
|
1414
|
+
cache: Whether to cache the computed value.
|
|
1415
|
+
deps: Explicit var dependencies to track.
|
|
1416
|
+
auto_deps: Whether var dependencies should be auto-determined.
|
|
1417
|
+
interval: Interval at which the computed var should be updated.
|
|
1418
|
+
backend: Whether the computed var is a backend var.
|
|
1419
|
+
**kwargs: additional attributes to set on the instance
|
|
1420
|
+
|
|
1421
|
+
Raises:
|
|
1422
|
+
TypeError: If the computed var dependencies are not Var instances or var names.
|
|
1423
|
+
"""
|
|
1424
|
+
hints = get_type_hints(fget)
|
|
1425
|
+
hint = hints.get("return", Any)
|
|
1426
|
+
|
|
1427
|
+
kwargs["_var_name"] = kwargs.pop("_var_name", fget.__name__)
|
|
1428
|
+
kwargs["_var_type"] = kwargs.pop("_var_type", hint)
|
|
1429
|
+
|
|
1430
|
+
ImmutableVar.__init__(
|
|
1431
|
+
self,
|
|
1432
|
+
_var_name=kwargs.pop("_var_name"),
|
|
1433
|
+
_var_type=kwargs.pop("_var_type"),
|
|
1434
|
+
_var_data=kwargs.pop("_var_data", None),
|
|
1435
|
+
)
|
|
1436
|
+
|
|
1437
|
+
if backend is None:
|
|
1438
|
+
backend = fget.__name__.startswith("_")
|
|
1439
|
+
|
|
1440
|
+
object.__setattr__(self, "_backend", backend)
|
|
1441
|
+
object.__setattr__(self, "_initial_value", initial_value)
|
|
1442
|
+
object.__setattr__(self, "_cache", cache)
|
|
1443
|
+
|
|
1444
|
+
if isinstance(interval, int):
|
|
1445
|
+
interval = datetime.timedelta(seconds=interval)
|
|
1446
|
+
|
|
1447
|
+
object.__setattr__(self, "_update_interval", interval)
|
|
1448
|
+
|
|
1449
|
+
if deps is None:
|
|
1450
|
+
deps = []
|
|
1451
|
+
else:
|
|
1452
|
+
for dep in deps:
|
|
1453
|
+
if isinstance(dep, ImmutableVar):
|
|
1454
|
+
continue
|
|
1455
|
+
if isinstance(dep, str) and dep != "":
|
|
1456
|
+
continue
|
|
1457
|
+
raise TypeError(
|
|
1458
|
+
"ComputedVar dependencies must be Var instances or var names (non-empty strings)."
|
|
1459
|
+
)
|
|
1460
|
+
object.__setattr__(
|
|
1461
|
+
self,
|
|
1462
|
+
"_static_deps",
|
|
1463
|
+
{dep._var_name if isinstance(dep, ImmutableVar) else dep for dep in deps},
|
|
1464
|
+
)
|
|
1465
|
+
object.__setattr__(self, "_auto_deps", auto_deps)
|
|
1466
|
+
|
|
1467
|
+
object.__setattr__(self, "_fget", fget)
|
|
1468
|
+
|
|
1469
|
+
@override
|
|
1470
|
+
def _replace(self, merge_var_data=None, **kwargs: Any) -> Self:
|
|
1471
|
+
"""Replace the attributes of the ComputedVar.
|
|
1472
|
+
|
|
1473
|
+
Args:
|
|
1474
|
+
merge_var_data: VarData to merge into the existing VarData.
|
|
1475
|
+
**kwargs: Var fields to update.
|
|
1476
|
+
|
|
1477
|
+
Returns:
|
|
1478
|
+
The new ComputedVar instance.
|
|
1479
|
+
|
|
1480
|
+
Raises:
|
|
1481
|
+
TypeError: If kwargs contains keys that are not allowed.
|
|
1482
|
+
"""
|
|
1483
|
+
field_values = dict(
|
|
1484
|
+
fget=kwargs.pop("fget", self._fget),
|
|
1485
|
+
initial_value=kwargs.pop("initial_value", self._initial_value),
|
|
1486
|
+
cache=kwargs.pop("cache", self._cache),
|
|
1487
|
+
deps=kwargs.pop("deps", self._static_deps),
|
|
1488
|
+
auto_deps=kwargs.pop("auto_deps", self._auto_deps),
|
|
1489
|
+
interval=kwargs.pop("interval", self._update_interval),
|
|
1490
|
+
backend=kwargs.pop("backend", self._backend),
|
|
1491
|
+
_var_name=kwargs.pop("_var_name", self._var_name),
|
|
1492
|
+
_var_type=kwargs.pop("_var_type", self._var_type),
|
|
1493
|
+
_var_data=kwargs.pop(
|
|
1494
|
+
"_var_data", VarData.merge(self._var_data, merge_var_data)
|
|
1495
|
+
),
|
|
1496
|
+
)
|
|
1497
|
+
|
|
1498
|
+
if kwargs:
|
|
1499
|
+
unexpected_kwargs = ", ".join(kwargs.keys())
|
|
1500
|
+
raise TypeError(f"Unexpected keyword arguments: {unexpected_kwargs}")
|
|
1501
|
+
|
|
1502
|
+
return type(self)(**field_values)
|
|
1503
|
+
|
|
1504
|
+
@property
|
|
1505
|
+
def _cache_attr(self) -> str:
|
|
1506
|
+
"""Get the attribute used to cache the value on the instance.
|
|
1507
|
+
|
|
1508
|
+
Returns:
|
|
1509
|
+
An attribute name.
|
|
1510
|
+
"""
|
|
1511
|
+
return f"__cached_{self._var_name}"
|
|
1512
|
+
|
|
1513
|
+
@property
|
|
1514
|
+
def _last_updated_attr(self) -> str:
|
|
1515
|
+
"""Get the attribute used to store the last updated timestamp.
|
|
1516
|
+
|
|
1517
|
+
Returns:
|
|
1518
|
+
An attribute name.
|
|
1519
|
+
"""
|
|
1520
|
+
return f"__last_updated_{self._var_name}"
|
|
1521
|
+
|
|
1522
|
+
def needs_update(self, instance: BaseState) -> bool:
|
|
1523
|
+
"""Check if the computed var needs to be updated.
|
|
1524
|
+
|
|
1525
|
+
Args:
|
|
1526
|
+
instance: The state instance that the computed var is attached to.
|
|
1527
|
+
|
|
1528
|
+
Returns:
|
|
1529
|
+
True if the computed var needs to be updated, False otherwise.
|
|
1530
|
+
"""
|
|
1531
|
+
if self._update_interval is None:
|
|
1532
|
+
return False
|
|
1533
|
+
last_updated = getattr(instance, self._last_updated_attr, None)
|
|
1534
|
+
if last_updated is None:
|
|
1535
|
+
return True
|
|
1536
|
+
return datetime.datetime.now() - last_updated > self._update_interval
|
|
1537
|
+
|
|
1538
|
+
@overload
|
|
1539
|
+
def __get__(
|
|
1540
|
+
self: ImmutableComputedVar[int] | ImmutableComputedVar[float],
|
|
1541
|
+
instance: None,
|
|
1542
|
+
owner: Type,
|
|
1543
|
+
) -> NumberVar: ...
|
|
1544
|
+
|
|
1545
|
+
@overload
|
|
1546
|
+
def __get__(
|
|
1547
|
+
self: ImmutableComputedVar[str],
|
|
1548
|
+
instance: None,
|
|
1549
|
+
owner: Type,
|
|
1550
|
+
) -> StringVar: ...
|
|
1551
|
+
|
|
1552
|
+
@overload
|
|
1553
|
+
def __get__(
|
|
1554
|
+
self: ImmutableComputedVar[dict[DICT_KEY, DICT_VAL]],
|
|
1555
|
+
instance: None,
|
|
1556
|
+
owner: Type,
|
|
1557
|
+
) -> ObjectVar[dict[DICT_KEY, DICT_VAL]]: ...
|
|
1558
|
+
|
|
1559
|
+
@overload
|
|
1560
|
+
def __get__(
|
|
1561
|
+
self: ImmutableComputedVar[list[LIST_INSIDE]],
|
|
1562
|
+
instance: None,
|
|
1563
|
+
owner: Type,
|
|
1564
|
+
) -> ArrayVar[list[LIST_INSIDE]]: ...
|
|
1565
|
+
|
|
1566
|
+
@overload
|
|
1567
|
+
def __get__(
|
|
1568
|
+
self: ImmutableComputedVar[set[LIST_INSIDE]],
|
|
1569
|
+
instance: None,
|
|
1570
|
+
owner: Type,
|
|
1571
|
+
) -> ArrayVar[set[LIST_INSIDE]]: ...
|
|
1572
|
+
|
|
1573
|
+
@overload
|
|
1574
|
+
def __get__(
|
|
1575
|
+
self: ImmutableComputedVar[tuple[LIST_INSIDE, ...]],
|
|
1576
|
+
instance: None,
|
|
1577
|
+
owner: Type,
|
|
1578
|
+
) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ...
|
|
1579
|
+
|
|
1580
|
+
@overload
|
|
1581
|
+
def __get__(
|
|
1582
|
+
self, instance: None, owner: Type
|
|
1583
|
+
) -> ImmutableComputedVar[RETURN_TYPE]: ...
|
|
1584
|
+
|
|
1585
|
+
@overload
|
|
1586
|
+
def __get__(self, instance: BaseState, owner: Type) -> RETURN_TYPE: ...
|
|
1587
|
+
|
|
1588
|
+
def __get__(self, instance: BaseState | None, owner):
|
|
1589
|
+
"""Get the ComputedVar value.
|
|
1590
|
+
|
|
1591
|
+
If the value is already cached on the instance, return the cached value.
|
|
1592
|
+
|
|
1593
|
+
Args:
|
|
1594
|
+
instance: the instance of the class accessing this computed var.
|
|
1595
|
+
owner: the class that this descriptor is attached to.
|
|
1596
|
+
|
|
1597
|
+
Returns:
|
|
1598
|
+
The value of the var for the given instance.
|
|
1599
|
+
"""
|
|
1600
|
+
if instance is None:
|
|
1601
|
+
state_where_defined = owner
|
|
1602
|
+
while self.fget.__name__ in state_where_defined.inherited_vars:
|
|
1603
|
+
state_where_defined = state_where_defined.get_parent_state()
|
|
1604
|
+
|
|
1605
|
+
return self._replace(
|
|
1606
|
+
_var_name=format_state_name(state_where_defined.get_full_name())
|
|
1607
|
+
+ "."
|
|
1608
|
+
+ self._var_name,
|
|
1609
|
+
merge_var_data=VarData.from_state(state_where_defined),
|
|
1610
|
+
).guess_type()
|
|
1611
|
+
|
|
1612
|
+
if not self._cache:
|
|
1613
|
+
return self.fget(instance)
|
|
1614
|
+
|
|
1615
|
+
# handle caching
|
|
1616
|
+
if not hasattr(instance, self._cache_attr) or self.needs_update(instance):
|
|
1617
|
+
# Set cache attr on state instance.
|
|
1618
|
+
setattr(instance, self._cache_attr, self.fget(instance))
|
|
1619
|
+
# Ensure the computed var gets serialized to redis.
|
|
1620
|
+
instance._was_touched = True
|
|
1621
|
+
# Set the last updated timestamp on the state instance.
|
|
1622
|
+
setattr(instance, self._last_updated_attr, datetime.datetime.now())
|
|
1623
|
+
return getattr(instance, self._cache_attr)
|
|
1624
|
+
|
|
1625
|
+
def _deps(
|
|
1626
|
+
self,
|
|
1627
|
+
objclass: Type,
|
|
1628
|
+
obj: FunctionType | CodeType | None = None,
|
|
1629
|
+
self_name: Optional[str] = None,
|
|
1630
|
+
) -> set[str]:
|
|
1631
|
+
"""Determine var dependencies of this ComputedVar.
|
|
1632
|
+
|
|
1633
|
+
Save references to attributes accessed on "self". Recursively called
|
|
1634
|
+
when the function makes a method call on "self" or define comprehensions
|
|
1635
|
+
or nested functions that may reference "self".
|
|
1636
|
+
|
|
1637
|
+
Args:
|
|
1638
|
+
objclass: the class obj this ComputedVar is attached to.
|
|
1639
|
+
obj: the object to disassemble (defaults to the fget function).
|
|
1640
|
+
self_name: if specified, look for this name in LOAD_FAST and LOAD_DEREF instructions.
|
|
1641
|
+
|
|
1642
|
+
Returns:
|
|
1643
|
+
A set of variable names accessed by the given obj.
|
|
1644
|
+
|
|
1645
|
+
Raises:
|
|
1646
|
+
VarValueError: if the function references the get_state, parent_state, or substates attributes
|
|
1647
|
+
(cannot track deps in a related state, only implicitly via parent state).
|
|
1648
|
+
"""
|
|
1649
|
+
if not self._auto_deps:
|
|
1650
|
+
return self._static_deps
|
|
1651
|
+
d = self._static_deps.copy()
|
|
1652
|
+
if obj is None:
|
|
1653
|
+
fget = self._fget
|
|
1654
|
+
if fget is not None:
|
|
1655
|
+
obj = cast(FunctionType, fget)
|
|
1656
|
+
else:
|
|
1657
|
+
return set()
|
|
1658
|
+
with contextlib.suppress(AttributeError):
|
|
1659
|
+
# unbox functools.partial
|
|
1660
|
+
obj = cast(FunctionType, obj.func) # type: ignore
|
|
1661
|
+
with contextlib.suppress(AttributeError):
|
|
1662
|
+
# unbox EventHandler
|
|
1663
|
+
obj = cast(FunctionType, obj.fn) # type: ignore
|
|
1664
|
+
|
|
1665
|
+
if self_name is None and isinstance(obj, FunctionType):
|
|
1666
|
+
try:
|
|
1667
|
+
# the first argument to the function is the name of "self" arg
|
|
1668
|
+
self_name = obj.__code__.co_varnames[0]
|
|
1669
|
+
except (AttributeError, IndexError):
|
|
1670
|
+
self_name = None
|
|
1671
|
+
if self_name is None:
|
|
1672
|
+
# cannot reference attributes on self if method takes no args
|
|
1673
|
+
return set()
|
|
1674
|
+
|
|
1675
|
+
invalid_names = ["get_state", "parent_state", "substates", "get_substate"]
|
|
1676
|
+
self_is_top_of_stack = False
|
|
1677
|
+
for instruction in dis.get_instructions(obj):
|
|
1678
|
+
if (
|
|
1679
|
+
instruction.opname in ("LOAD_FAST", "LOAD_DEREF")
|
|
1680
|
+
and instruction.argval == self_name
|
|
1681
|
+
):
|
|
1682
|
+
# bytecode loaded the class instance to the top of stack, next load instruction
|
|
1683
|
+
# is referencing an attribute on self
|
|
1684
|
+
self_is_top_of_stack = True
|
|
1685
|
+
continue
|
|
1686
|
+
if self_is_top_of_stack and instruction.opname in (
|
|
1687
|
+
"LOAD_ATTR",
|
|
1688
|
+
"LOAD_METHOD",
|
|
1689
|
+
):
|
|
1690
|
+
try:
|
|
1691
|
+
ref_obj = getattr(objclass, instruction.argval)
|
|
1692
|
+
except Exception:
|
|
1693
|
+
ref_obj = None
|
|
1694
|
+
if instruction.argval in invalid_names:
|
|
1695
|
+
raise VarValueError(
|
|
1696
|
+
f"Cached var {str(self)} cannot access arbitrary state via `{instruction.argval}`."
|
|
1697
|
+
)
|
|
1698
|
+
if callable(ref_obj):
|
|
1699
|
+
# recurse into callable attributes
|
|
1700
|
+
d.update(
|
|
1701
|
+
self._deps(
|
|
1702
|
+
objclass=objclass,
|
|
1703
|
+
obj=ref_obj,
|
|
1704
|
+
)
|
|
1705
|
+
)
|
|
1706
|
+
# recurse into property fget functions
|
|
1707
|
+
elif isinstance(ref_obj, property) and not isinstance(
|
|
1708
|
+
ref_obj, ImmutableComputedVar
|
|
1709
|
+
):
|
|
1710
|
+
d.update(
|
|
1711
|
+
self._deps(
|
|
1712
|
+
objclass=objclass,
|
|
1713
|
+
obj=ref_obj.fget, # type: ignore
|
|
1714
|
+
)
|
|
1715
|
+
)
|
|
1716
|
+
elif (
|
|
1717
|
+
instruction.argval in objclass.backend_vars
|
|
1718
|
+
or instruction.argval in objclass.vars
|
|
1719
|
+
):
|
|
1720
|
+
# var access
|
|
1721
|
+
d.add(instruction.argval)
|
|
1722
|
+
elif instruction.opname == "LOAD_CONST" and isinstance(
|
|
1723
|
+
instruction.argval, CodeType
|
|
1724
|
+
):
|
|
1725
|
+
# recurse into nested functions / comprehensions, which can reference
|
|
1726
|
+
# instance attributes from the outer scope
|
|
1727
|
+
d.update(
|
|
1728
|
+
self._deps(
|
|
1729
|
+
objclass=objclass,
|
|
1730
|
+
obj=instruction.argval,
|
|
1731
|
+
self_name=self_name,
|
|
1732
|
+
)
|
|
1733
|
+
)
|
|
1734
|
+
self_is_top_of_stack = False
|
|
1735
|
+
return d
|
|
1736
|
+
|
|
1737
|
+
def mark_dirty(self, instance) -> None:
|
|
1738
|
+
"""Mark this ComputedVar as dirty.
|
|
1739
|
+
|
|
1740
|
+
Args:
|
|
1741
|
+
instance: the state instance that needs to recompute the value.
|
|
1742
|
+
"""
|
|
1743
|
+
with contextlib.suppress(AttributeError):
|
|
1744
|
+
delattr(instance, self._cache_attr)
|
|
1745
|
+
|
|
1746
|
+
def _determine_var_type(self) -> Type:
|
|
1747
|
+
"""Get the type of the var.
|
|
1748
|
+
|
|
1749
|
+
Returns:
|
|
1750
|
+
The type of the var.
|
|
1751
|
+
"""
|
|
1752
|
+
hints = get_type_hints(self._fget)
|
|
1753
|
+
if "return" in hints:
|
|
1754
|
+
return hints["return"]
|
|
1755
|
+
return Any
|
|
1756
|
+
|
|
1757
|
+
@property
|
|
1758
|
+
def __class__(self) -> Type:
|
|
1759
|
+
"""Get the class of the var.
|
|
1760
|
+
|
|
1761
|
+
Returns:
|
|
1762
|
+
The class of the var.
|
|
1763
|
+
"""
|
|
1764
|
+
return FakeComputedVarBaseClass
|
|
1765
|
+
|
|
1766
|
+
@property
|
|
1767
|
+
def fget(self) -> Callable[[BaseState], RETURN_TYPE]:
|
|
1768
|
+
"""Get the getter function.
|
|
1769
|
+
|
|
1770
|
+
Returns:
|
|
1771
|
+
The getter function.
|
|
1772
|
+
"""
|
|
1773
|
+
return self._fget
|
|
1774
|
+
|
|
1775
|
+
|
|
1776
|
+
class DynamicRouteVar(ImmutableComputedVar[Union[str, List[str]]]):
|
|
1777
|
+
"""A ComputedVar that represents a dynamic route."""
|
|
1778
|
+
|
|
1779
|
+
pass
|
|
1780
|
+
|
|
1781
|
+
|
|
1782
|
+
if TYPE_CHECKING:
|
|
1783
|
+
BASE_STATE = TypeVar("BASE_STATE", bound=BaseState)
|
|
1784
|
+
|
|
1785
|
+
|
|
1786
|
+
@overload
|
|
1787
|
+
def immutable_computed_var(
|
|
1788
|
+
fget: None = None,
|
|
1789
|
+
initial_value: Any | types.Unset = types.Unset(),
|
|
1790
|
+
cache: bool = False,
|
|
1791
|
+
deps: Optional[List[Union[str, ImmutableVar]]] = None,
|
|
1792
|
+
auto_deps: bool = True,
|
|
1793
|
+
interval: Optional[Union[datetime.timedelta, int]] = None,
|
|
1794
|
+
backend: bool | None = None,
|
|
1795
|
+
**kwargs,
|
|
1796
|
+
) -> Callable[
|
|
1797
|
+
[Callable[[BASE_STATE], RETURN_TYPE]], ImmutableComputedVar[RETURN_TYPE]
|
|
1798
|
+
]: ...
|
|
1799
|
+
|
|
1800
|
+
|
|
1801
|
+
@overload
|
|
1802
|
+
def immutable_computed_var(
|
|
1803
|
+
fget: Callable[[BASE_STATE], RETURN_TYPE],
|
|
1804
|
+
initial_value: RETURN_TYPE | types.Unset = types.Unset(),
|
|
1805
|
+
cache: bool = False,
|
|
1806
|
+
deps: Optional[List[Union[str, ImmutableVar]]] = None,
|
|
1807
|
+
auto_deps: bool = True,
|
|
1808
|
+
interval: Optional[Union[datetime.timedelta, int]] = None,
|
|
1809
|
+
backend: bool | None = None,
|
|
1810
|
+
**kwargs,
|
|
1811
|
+
) -> ImmutableComputedVar[RETURN_TYPE]: ...
|
|
1812
|
+
|
|
1813
|
+
|
|
1814
|
+
def immutable_computed_var(
|
|
1815
|
+
fget: Callable[[BASE_STATE], Any] | None = None,
|
|
1816
|
+
initial_value: Any | types.Unset = types.Unset(),
|
|
1817
|
+
cache: bool = False,
|
|
1818
|
+
deps: Optional[List[Union[str, ImmutableVar]]] = None,
|
|
1819
|
+
auto_deps: bool = True,
|
|
1820
|
+
interval: Optional[Union[datetime.timedelta, int]] = None,
|
|
1821
|
+
backend: bool | None = None,
|
|
1822
|
+
**kwargs,
|
|
1823
|
+
) -> (
|
|
1824
|
+
ImmutableComputedVar | Callable[[Callable[[BASE_STATE], Any]], ImmutableComputedVar]
|
|
1825
|
+
):
|
|
1826
|
+
"""A ComputedVar decorator with or without kwargs.
|
|
1827
|
+
|
|
1828
|
+
Args:
|
|
1829
|
+
fget: The getter function.
|
|
1830
|
+
initial_value: The initial value of the computed var.
|
|
1831
|
+
cache: Whether to cache the computed value.
|
|
1832
|
+
deps: Explicit var dependencies to track.
|
|
1833
|
+
auto_deps: Whether var dependencies should be auto-determined.
|
|
1834
|
+
interval: Interval at which the computed var should be updated.
|
|
1835
|
+
backend: Whether the computed var is a backend var.
|
|
1836
|
+
**kwargs: additional attributes to set on the instance
|
|
1837
|
+
|
|
1838
|
+
Returns:
|
|
1839
|
+
A ComputedVar instance.
|
|
1840
|
+
|
|
1841
|
+
Raises:
|
|
1842
|
+
ValueError: If caching is disabled and an update interval is set.
|
|
1843
|
+
VarDependencyError: If user supplies dependencies without caching.
|
|
1844
|
+
"""
|
|
1845
|
+
if cache is False and interval is not None:
|
|
1846
|
+
raise ValueError("Cannot set update interval without caching.")
|
|
1847
|
+
|
|
1848
|
+
if cache is False and (deps is not None or auto_deps is False):
|
|
1849
|
+
raise VarDependencyError("Cannot track dependencies without caching.")
|
|
1850
|
+
|
|
1851
|
+
if fget is not None:
|
|
1852
|
+
return ImmutableComputedVar(fget, cache=cache)
|
|
1853
|
+
|
|
1854
|
+
def wrapper(fget: Callable[[BASE_STATE], Any]) -> ImmutableComputedVar:
|
|
1855
|
+
return ImmutableComputedVar(
|
|
1856
|
+
fget,
|
|
1857
|
+
initial_value=initial_value,
|
|
1858
|
+
cache=cache,
|
|
1859
|
+
deps=deps,
|
|
1860
|
+
auto_deps=auto_deps,
|
|
1861
|
+
interval=interval,
|
|
1862
|
+
backend=backend,
|
|
1863
|
+
**kwargs,
|
|
1864
|
+
)
|
|
1865
|
+
|
|
1866
|
+
return wrapper
|
|
1867
|
+
|
|
1868
|
+
|
|
1869
|
+
RETURN = TypeVar("RETURN")
|
|
1870
|
+
|
|
1871
|
+
|
|
1872
|
+
class CustomVarOperationReturn(ImmutableVar[RETURN]):
|
|
1873
|
+
"""Base class for custom var operations."""
|
|
1874
|
+
|
|
1875
|
+
@classmethod
|
|
1876
|
+
def create(
|
|
1877
|
+
cls,
|
|
1878
|
+
js_expression: str,
|
|
1879
|
+
_var_type: Type[RETURN] | None = None,
|
|
1880
|
+
_var_data: VarData | None = None,
|
|
1881
|
+
) -> CustomVarOperationReturn[RETURN]:
|
|
1882
|
+
"""Create a CustomVarOperation.
|
|
1883
|
+
|
|
1884
|
+
Args:
|
|
1885
|
+
js_expression: The JavaScript expression to evaluate.
|
|
1886
|
+
_var_type: The type of the var.
|
|
1887
|
+
_var_data: Additional hooks and imports associated with the Var.
|
|
1888
|
+
|
|
1889
|
+
Returns:
|
|
1890
|
+
The CustomVarOperation.
|
|
1891
|
+
"""
|
|
1892
|
+
return CustomVarOperationReturn(
|
|
1893
|
+
_var_name=js_expression,
|
|
1894
|
+
_var_type=_var_type or Any,
|
|
1895
|
+
_var_data=_var_data,
|
|
1896
|
+
)
|
|
1897
|
+
|
|
1898
|
+
|
|
1899
|
+
def var_operation_return(
|
|
1900
|
+
js_expression: str,
|
|
1901
|
+
var_type: Type[RETURN] | None = None,
|
|
1902
|
+
) -> CustomVarOperationReturn[RETURN]:
|
|
1903
|
+
"""Shortcut for creating a CustomVarOperationReturn.
|
|
1904
|
+
|
|
1905
|
+
Args:
|
|
1906
|
+
js_expression: The JavaScript expression to evaluate.
|
|
1907
|
+
var_type: The type of the var.
|
|
1908
|
+
|
|
1909
|
+
Returns:
|
|
1910
|
+
The CustomVarOperationReturn.
|
|
1911
|
+
"""
|
|
1912
|
+
return CustomVarOperationReturn.create(js_expression, var_type)
|
|
1913
|
+
|
|
1914
|
+
|
|
1915
|
+
@dataclasses.dataclass(
|
|
1916
|
+
eq=False,
|
|
1917
|
+
frozen=True,
|
|
1918
|
+
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
|
1919
|
+
)
|
|
1920
|
+
class CustomVarOperation(CachedVarOperation, ImmutableVar[T]):
|
|
1921
|
+
"""Base class for custom var operations."""
|
|
1922
|
+
|
|
1923
|
+
_args: Tuple[Tuple[str, ImmutableVar], ...] = dataclasses.field(
|
|
1924
|
+
default_factory=tuple
|
|
1925
|
+
)
|
|
1926
|
+
|
|
1927
|
+
_return: CustomVarOperationReturn[T] = dataclasses.field(
|
|
1928
|
+
default_factory=lambda: CustomVarOperationReturn.create("")
|
|
1929
|
+
)
|
|
1930
|
+
|
|
1931
|
+
@cached_property_no_lock
|
|
1932
|
+
def _cached_var_name(self) -> str:
|
|
1933
|
+
"""Get the cached var name.
|
|
1934
|
+
|
|
1935
|
+
Returns:
|
|
1936
|
+
The cached var name.
|
|
1937
|
+
"""
|
|
1938
|
+
return str(self._return)
|
|
1939
|
+
|
|
1940
|
+
@cached_property_no_lock
|
|
1941
|
+
def _cached_get_all_var_data(self) -> VarData | None:
|
|
1942
|
+
"""Get the cached VarData.
|
|
1943
|
+
|
|
1944
|
+
Returns:
|
|
1945
|
+
The cached VarData.
|
|
1946
|
+
"""
|
|
1947
|
+
return VarData.merge(
|
|
1948
|
+
*map(
|
|
1949
|
+
lambda arg: arg[1]._get_all_var_data(),
|
|
1950
|
+
self._args,
|
|
1951
|
+
),
|
|
1952
|
+
self._return._get_all_var_data(),
|
|
1953
|
+
self._var_data,
|
|
1954
|
+
)
|
|
1955
|
+
|
|
1956
|
+
@classmethod
|
|
1957
|
+
def create(
|
|
1958
|
+
cls,
|
|
1959
|
+
args: Tuple[Tuple[str, ImmutableVar], ...],
|
|
1960
|
+
return_var: CustomVarOperationReturn[T],
|
|
1961
|
+
_var_data: VarData | None = None,
|
|
1962
|
+
) -> CustomVarOperation[T]:
|
|
1963
|
+
"""Create a CustomVarOperation.
|
|
1964
|
+
|
|
1965
|
+
Args:
|
|
1966
|
+
args: The arguments to the operation.
|
|
1967
|
+
return_var: The return var.
|
|
1968
|
+
_var_data: Additional hooks and imports associated with the Var.
|
|
1969
|
+
|
|
1970
|
+
Returns:
|
|
1971
|
+
The CustomVarOperation.
|
|
1972
|
+
"""
|
|
1973
|
+
return CustomVarOperation(
|
|
1974
|
+
_var_name="",
|
|
1975
|
+
_var_type=return_var._var_type,
|
|
1976
|
+
_var_data=_var_data,
|
|
1977
|
+
_args=args,
|
|
1978
|
+
_return=return_var,
|
|
1979
|
+
)
|
|
1980
|
+
|
|
1981
|
+
|
|
1982
|
+
class NoneVar(ImmutableVar[None]):
|
|
1983
|
+
"""A var representing None."""
|
|
1984
|
+
|
|
1985
|
+
|
|
1986
|
+
class LiteralNoneVar(LiteralVar, NoneVar):
|
|
1987
|
+
"""A var representing None."""
|
|
1988
|
+
|
|
1989
|
+
def json(self) -> str:
|
|
1990
|
+
"""Serialize the var to a JSON string.
|
|
1991
|
+
|
|
1992
|
+
Returns:
|
|
1993
|
+
The JSON string.
|
|
1994
|
+
"""
|
|
1995
|
+
return "null"
|
|
1996
|
+
|
|
1997
|
+
@classmethod
|
|
1998
|
+
def create(
|
|
1999
|
+
cls,
|
|
2000
|
+
_var_data: VarData | None = None,
|
|
2001
|
+
) -> LiteralNoneVar:
|
|
2002
|
+
"""Create a var from a value.
|
|
2003
|
+
|
|
2004
|
+
Args:
|
|
2005
|
+
_var_data: Additional hooks and imports associated with the Var.
|
|
2006
|
+
|
|
2007
|
+
Returns:
|
|
2008
|
+
The var.
|
|
2009
|
+
"""
|
|
2010
|
+
return LiteralNoneVar(
|
|
2011
|
+
_var_name="null",
|
|
2012
|
+
_var_type=None,
|
|
2013
|
+
_var_data=_var_data,
|
|
2014
|
+
)
|
|
2015
|
+
|
|
2016
|
+
|
|
2017
|
+
@dataclasses.dataclass(
|
|
2018
|
+
eq=False,
|
|
2019
|
+
frozen=True,
|
|
2020
|
+
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
|
2021
|
+
)
|
|
2022
|
+
class ToNoneOperation(CachedVarOperation, NoneVar):
|
|
2023
|
+
"""A var operation that converts a var to None."""
|
|
2024
|
+
|
|
2025
|
+
_original_var: Var = dataclasses.field(
|
|
2026
|
+
default_factory=lambda: LiteralNoneVar.create()
|
|
2027
|
+
)
|
|
2028
|
+
|
|
2029
|
+
@cached_property_no_lock
|
|
2030
|
+
def _cached_var_name(self) -> str:
|
|
2031
|
+
"""Get the cached var name.
|
|
2032
|
+
|
|
2033
|
+
Returns:
|
|
2034
|
+
The cached var name.
|
|
2035
|
+
"""
|
|
2036
|
+
return str(self._original_var)
|
|
2037
|
+
|
|
2038
|
+
@classmethod
|
|
2039
|
+
def create(
|
|
2040
|
+
cls,
|
|
2041
|
+
var: Var,
|
|
2042
|
+
_var_data: VarData | None = None,
|
|
2043
|
+
) -> ToNoneOperation:
|
|
2044
|
+
"""Create a ToNoneOperation.
|
|
2045
|
+
|
|
2046
|
+
Args:
|
|
2047
|
+
var: The var to convert to None.
|
|
2048
|
+
_var_data: Additional hooks and imports associated with the Var.
|
|
2049
|
+
|
|
2050
|
+
Returns:
|
|
2051
|
+
The ToNoneOperation.
|
|
2052
|
+
"""
|
|
2053
|
+
return ToNoneOperation(
|
|
2054
|
+
_var_name="",
|
|
2055
|
+
_var_type=None,
|
|
2056
|
+
_var_data=_var_data,
|
|
2057
|
+
_original_var=var,
|
|
2058
|
+
)
|
|
2059
|
+
|
|
2060
|
+
|
|
2061
|
+
@dataclasses.dataclass(
|
|
2062
|
+
eq=False,
|
|
2063
|
+
frozen=True,
|
|
2064
|
+
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
|
2065
|
+
)
|
|
2066
|
+
class StateOperation(CachedVarOperation, ImmutableVar):
|
|
2067
|
+
"""A var operation that accesses a field on an object."""
|
|
2068
|
+
|
|
2069
|
+
_state_name: str = dataclasses.field(default="")
|
|
2070
|
+
_field: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
|
|
2071
|
+
|
|
2072
|
+
@cached_property_no_lock
|
|
2073
|
+
def _cached_var_name(self) -> str:
|
|
2074
|
+
"""Get the cached var name.
|
|
2075
|
+
|
|
2076
|
+
Returns:
|
|
2077
|
+
The cached var name.
|
|
2078
|
+
"""
|
|
2079
|
+
return f"{str(self._state_name)}.{str(self._field)}"
|
|
2080
|
+
|
|
2081
|
+
def __getattr__(self, name: str) -> Any:
|
|
2082
|
+
"""Get an attribute of the var.
|
|
2083
|
+
|
|
2084
|
+
Args:
|
|
2085
|
+
name: The name of the attribute.
|
|
2086
|
+
|
|
2087
|
+
Returns:
|
|
2088
|
+
The attribute.
|
|
2089
|
+
"""
|
|
2090
|
+
if name == "_var_name":
|
|
2091
|
+
return self._cached_var_name
|
|
2092
|
+
|
|
2093
|
+
return getattr(self._field, name)
|
|
2094
|
+
|
|
2095
|
+
@classmethod
|
|
2096
|
+
def create(
|
|
2097
|
+
cls,
|
|
2098
|
+
state_name: str,
|
|
2099
|
+
field: ImmutableVar,
|
|
2100
|
+
_var_data: VarData | None = None,
|
|
2101
|
+
) -> StateOperation:
|
|
2102
|
+
"""Create a DotOperation.
|
|
2103
|
+
|
|
2104
|
+
Args:
|
|
2105
|
+
state_name: The name of the state.
|
|
2106
|
+
field: The field of the state.
|
|
2107
|
+
_var_data: Additional hooks and imports associated with the Var.
|
|
2108
|
+
|
|
2109
|
+
Returns:
|
|
2110
|
+
The DotOperation.
|
|
2111
|
+
"""
|
|
2112
|
+
return StateOperation(
|
|
2113
|
+
_var_name="",
|
|
2114
|
+
_var_type=field._var_type,
|
|
2115
|
+
_var_data=_var_data,
|
|
2116
|
+
_state_name=state_name,
|
|
2117
|
+
_field=field,
|
|
2118
|
+
)
|
|
2119
|
+
|
|
2120
|
+
|
|
2121
|
+
class ToOperation:
|
|
2122
|
+
"""A var operation that converts a var to another type."""
|
|
2123
|
+
|
|
2124
|
+
def __getattr__(self, name: str) -> Any:
|
|
2125
|
+
"""Get an attribute of the var.
|
|
2126
|
+
|
|
2127
|
+
Args:
|
|
2128
|
+
name: The name of the attribute.
|
|
2129
|
+
|
|
2130
|
+
Returns:
|
|
2131
|
+
The attribute of the var.
|
|
2132
|
+
"""
|
|
2133
|
+
return getattr(object.__getattribute__(self, "_original"), name)
|
|
2134
|
+
|
|
2135
|
+
def __post_init__(self):
|
|
2136
|
+
"""Post initialization."""
|
|
2137
|
+
object.__delattr__(self, "_var_name")
|
|
2138
|
+
|
|
2139
|
+
def __hash__(self) -> int:
|
|
2140
|
+
"""Calculate the hash value of the object.
|
|
2141
|
+
|
|
2142
|
+
Returns:
|
|
2143
|
+
int: The hash value of the object.
|
|
2144
|
+
"""
|
|
2145
|
+
return hash(object.__getattribute__(self, "_original"))
|
|
2146
|
+
|
|
2147
|
+
def _get_all_var_data(self) -> VarData | None:
|
|
2148
|
+
"""Get all the var data.
|
|
2149
|
+
|
|
2150
|
+
Returns:
|
|
2151
|
+
The var data.
|
|
2152
|
+
"""
|
|
2153
|
+
return VarData.merge(
|
|
2154
|
+
object.__getattribute__(self, "_original")._get_all_var_data(),
|
|
2155
|
+
self._var_data, # type: ignore
|
|
2156
|
+
)
|
|
2157
|
+
|
|
2158
|
+
@classmethod
|
|
2159
|
+
def create(
|
|
2160
|
+
cls,
|
|
2161
|
+
value: Var,
|
|
2162
|
+
_var_type: GenericType | None = None,
|
|
2163
|
+
_var_data: VarData | None = None,
|
|
2164
|
+
):
|
|
2165
|
+
"""Create a ToOperation.
|
|
2166
|
+
|
|
2167
|
+
Args:
|
|
2168
|
+
value: The value of the var.
|
|
2169
|
+
_var_type: The type of the Var.
|
|
2170
|
+
_var_data: Additional hooks and imports associated with the Var.
|
|
2171
|
+
|
|
2172
|
+
Returns:
|
|
2173
|
+
The ToOperation.
|
|
2174
|
+
"""
|
|
2175
|
+
return cls(
|
|
2176
|
+
_var_name="", # type: ignore
|
|
2177
|
+
_var_data=_var_data, # type: ignore
|
|
2178
|
+
_var_type=_var_type or cls._default_var_type, # type: ignore
|
|
2179
|
+
_original=value, # type: ignore
|
|
2180
|
+
)
|