reflex 0.6.8a1__py3-none-any.whl → 0.7.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/custom_components/pyproject.toml.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/_app.js.jinja2 +7 -7
- reflex/.templates/jinja/web/pages/utils.js.jinja2 +2 -2
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +1 -4
- reflex/.templates/web/utils/state.js +65 -36
- reflex/__init__.py +4 -17
- reflex/__init__.pyi +1 -2
- reflex/app.py +244 -115
- reflex/app_mixins/lifespan.py +9 -9
- reflex/app_mixins/middleware.py +6 -6
- reflex/app_module_for_backend.py +3 -7
- reflex/base.py +7 -7
- reflex/compiler/compiler.py +8 -0
- reflex/compiler/utils.py +35 -6
- reflex/components/base/bare.py +1 -1
- reflex/components/base/error_boundary.py +2 -1
- reflex/components/base/error_boundary.pyi +2 -1
- reflex/components/base/meta.py +2 -2
- reflex/components/base/strict_mode.py +10 -0
- reflex/components/base/strict_mode.pyi +57 -0
- reflex/components/component.py +38 -77
- reflex/components/core/banner.py +83 -4
- reflex/components/core/banner.pyi +86 -0
- reflex/components/core/breakpoints.py +3 -1
- reflex/components/core/client_side_routing.py +1 -1
- reflex/components/core/client_side_routing.pyi +1 -1
- reflex/components/core/cond.py +9 -10
- reflex/components/core/debounce.py +1 -1
- reflex/components/core/foreach.py +23 -3
- reflex/components/core/html.py +1 -1
- reflex/components/core/match.py +5 -5
- reflex/components/core/sticky.py +160 -0
- reflex/components/core/sticky.pyi +449 -0
- reflex/components/core/upload.py +2 -2
- reflex/components/datadisplay/code.py +5 -14
- reflex/components/datadisplay/dataeditor.py +7 -4
- reflex/components/datadisplay/logo.py +13 -8
- reflex/components/datadisplay/shiki_code_block.py +14 -9
- reflex/components/dynamic.py +22 -3
- reflex/components/el/constants/reflex.py +1 -1
- reflex/components/el/element.py +1 -1
- reflex/components/el/elements/forms.py +4 -4
- reflex/components/el/elements/forms.pyi +4 -4
- reflex/components/lucide/icon.py +46 -8
- reflex/components/lucide/icon.pyi +54 -0
- reflex/components/markdown/markdown.py +10 -8
- reflex/components/moment/moment.py +2 -2
- reflex/components/next/image.py +16 -4
- reflex/components/next/image.pyi +4 -2
- reflex/components/next/link.py +1 -1
- reflex/components/plotly/plotly.py +5 -5
- reflex/components/props.py +3 -3
- reflex/components/radix/__init__.pyi +1 -1
- reflex/components/radix/primitives/accordion.py +9 -5
- reflex/components/radix/primitives/accordion.pyi +3 -1
- reflex/components/radix/primitives/drawer.py +5 -2
- reflex/components/radix/primitives/drawer.pyi +4 -4
- reflex/components/radix/primitives/form.pyi +6 -6
- reflex/components/radix/primitives/progress.py +1 -1
- reflex/components/radix/primitives/slider.py +1 -1
- reflex/components/radix/themes/color_mode.py +11 -9
- reflex/components/radix/themes/components/alert_dialog.py +3 -0
- reflex/components/radix/themes/components/card.py +1 -1
- reflex/components/radix/themes/components/card.pyi +1 -1
- reflex/components/radix/themes/components/context_menu.py +5 -0
- reflex/components/radix/themes/components/dialog.py +3 -0
- reflex/components/radix/themes/components/dropdown_menu.py +5 -0
- reflex/components/radix/themes/components/hover_card.py +3 -0
- reflex/components/radix/themes/components/icon_button.py +2 -2
- reflex/components/radix/themes/components/icon_button.pyi +1 -0
- reflex/components/radix/themes/components/popover.py +3 -0
- reflex/components/radix/themes/components/radio_cards.py +2 -0
- reflex/components/radix/themes/components/radio_group.py +1 -1
- reflex/components/radix/themes/components/select.py +3 -0
- reflex/components/radix/themes/components/tabs.py +3 -0
- reflex/components/radix/themes/components/text_area.py +12 -0
- reflex/components/radix/themes/components/text_area.pyi +2 -0
- reflex/components/radix/themes/components/text_field.py +1 -1
- reflex/components/radix/themes/components/tooltip.py +3 -1
- reflex/components/radix/themes/components/tooltip.pyi +1 -0
- reflex/components/radix/themes/layout/__init__.pyi +1 -1
- reflex/components/radix/themes/layout/list.py +2 -2
- reflex/components/radix/themes/layout/stack.py +2 -2
- reflex/components/radix/themes/typography/link.py +1 -1
- reflex/components/radix/themes/typography/text.py +2 -2
- reflex/components/react_player/react_player.py +1 -1
- reflex/components/recharts/__init__.py +2 -0
- reflex/components/recharts/__init__.pyi +2 -0
- reflex/components/recharts/charts.py +15 -15
- reflex/components/recharts/general.py +19 -4
- reflex/components/recharts/general.pyi +55 -4
- reflex/components/recharts/polar.py +2 -2
- reflex/components/recharts/recharts.py +4 -4
- reflex/components/sonner/toast.py +15 -13
- reflex/components/sonner/toast.pyi +6 -6
- reflex/components/suneditor/editor.py +6 -4
- reflex/components/suneditor/editor.pyi +2 -2
- reflex/components/tags/iter_tag.py +3 -3
- reflex/components/tags/tag.py +25 -3
- reflex/config.py +48 -20
- reflex/constants/__init__.py +1 -0
- reflex/constants/base.py +4 -1
- reflex/constants/compiler.py +5 -2
- reflex/constants/config.py +8 -1
- reflex/constants/installer.py +9 -9
- reflex/constants/style.py +1 -1
- reflex/custom_components/custom_components.py +9 -7
- reflex/event.py +137 -163
- reflex/experimental/__init__.py +19 -11
- reflex/experimental/client_state.py +53 -28
- reflex/experimental/hooks.py +5 -5
- reflex/experimental/layout.py +8 -5
- reflex/experimental/layout.pyi +1 -1
- reflex/experimental/misc.py +3 -3
- reflex/istate/wrappers.py +1 -1
- reflex/middleware/hydrate_middleware.py +2 -2
- reflex/model.py +11 -6
- reflex/page.py +3 -3
- reflex/reflex.py +90 -19
- reflex/route.py +1 -1
- reflex/state.py +358 -401
- reflex/style.py +27 -3
- reflex/testing.py +34 -39
- reflex/utils/build.py +6 -2
- reflex/utils/codespaces.py +1 -4
- reflex/utils/compat.py +6 -5
- reflex/utils/console.py +52 -21
- reflex/utils/exceptions.py +76 -26
- reflex/utils/exec.py +69 -74
- reflex/utils/export.py +6 -1
- reflex/utils/format.py +7 -39
- reflex/utils/imports.py +2 -2
- reflex/utils/lazy_loader.py +7 -1
- reflex/utils/path_ops.py +28 -14
- reflex/utils/prerequisites.py +324 -65
- reflex/utils/processes.py +45 -32
- reflex/utils/pyi_generator.py +30 -25
- reflex/utils/registry.py +4 -4
- reflex/utils/serializers.py +1 -1
- reflex/utils/telemetry.py +5 -4
- reflex/utils/types.py +42 -18
- reflex/vars/base.py +650 -333
- reflex/vars/datetime.py +6 -7
- reflex/vars/dep_tracking.py +344 -0
- reflex/vars/function.py +11 -5
- reflex/vars/number.py +31 -43
- reflex/vars/object.py +63 -62
- reflex/vars/sequence.py +79 -67
- {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/METADATA +7 -10
- {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/RECORD +153 -150
- {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/WHEEL +1 -1
- reflex/experimental/assets.py +0 -37
- reflex/proxy.py +0 -119
- {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/LICENSE +0 -0
- {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/entry_points.txt +0 -0
reflex/vars/datetime.py
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import dataclasses
|
|
6
|
-
import sys
|
|
7
6
|
from datetime import date, datetime
|
|
8
7
|
from typing import Any, NoReturn, TypeVar, Union, overload
|
|
9
8
|
|
|
@@ -40,7 +39,7 @@ class DateTimeVar(Var[DATETIME_T], python_types=(datetime, date)):
|
|
|
40
39
|
def __lt__(self, other: datetime_types) -> BooleanVar: ...
|
|
41
40
|
|
|
42
41
|
@overload
|
|
43
|
-
def __lt__(self, other: NoReturn) -> NoReturn: ...
|
|
42
|
+
def __lt__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
|
|
44
43
|
|
|
45
44
|
def __lt__(self, other: Any):
|
|
46
45
|
"""Less than comparison.
|
|
@@ -59,7 +58,7 @@ class DateTimeVar(Var[DATETIME_T], python_types=(datetime, date)):
|
|
|
59
58
|
def __le__(self, other: datetime_types) -> BooleanVar: ...
|
|
60
59
|
|
|
61
60
|
@overload
|
|
62
|
-
def __le__(self, other: NoReturn) -> NoReturn: ...
|
|
61
|
+
def __le__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
|
|
63
62
|
|
|
64
63
|
def __le__(self, other: Any):
|
|
65
64
|
"""Less than or equal comparison.
|
|
@@ -78,7 +77,7 @@ class DateTimeVar(Var[DATETIME_T], python_types=(datetime, date)):
|
|
|
78
77
|
def __gt__(self, other: datetime_types) -> BooleanVar: ...
|
|
79
78
|
|
|
80
79
|
@overload
|
|
81
|
-
def __gt__(self, other: NoReturn) -> NoReturn: ...
|
|
80
|
+
def __gt__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
|
|
82
81
|
|
|
83
82
|
def __gt__(self, other: Any):
|
|
84
83
|
"""Greater than comparison.
|
|
@@ -97,7 +96,7 @@ class DateTimeVar(Var[DATETIME_T], python_types=(datetime, date)):
|
|
|
97
96
|
def __ge__(self, other: datetime_types) -> BooleanVar: ...
|
|
98
97
|
|
|
99
98
|
@overload
|
|
100
|
-
def __ge__(self, other: NoReturn) -> NoReturn: ...
|
|
99
|
+
def __ge__(self, other: NoReturn) -> NoReturn: ... # pyright: ignore [reportOverlappingOverload]
|
|
101
100
|
|
|
102
101
|
def __ge__(self, other: Any):
|
|
103
102
|
"""Greater than or equal comparison.
|
|
@@ -185,7 +184,7 @@ def date_compare_operation(
|
|
|
185
184
|
The result of the operation.
|
|
186
185
|
"""
|
|
187
186
|
return var_operation_return(
|
|
188
|
-
f"({lhs} {
|
|
187
|
+
f"({lhs} {'<' if strict else '<='} {rhs})",
|
|
189
188
|
bool,
|
|
190
189
|
)
|
|
191
190
|
|
|
@@ -193,7 +192,7 @@ def date_compare_operation(
|
|
|
193
192
|
@dataclasses.dataclass(
|
|
194
193
|
eq=False,
|
|
195
194
|
frozen=True,
|
|
196
|
-
|
|
195
|
+
slots=True,
|
|
197
196
|
)
|
|
198
197
|
class LiteralDatetimeVar(LiteralVar, DateTimeVar):
|
|
199
198
|
"""Base class for immutable datetime and date vars."""
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""Collection of base classes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import contextlib
|
|
6
|
+
import dataclasses
|
|
7
|
+
import dis
|
|
8
|
+
import enum
|
|
9
|
+
import inspect
|
|
10
|
+
from types import CellType, CodeType, FunctionType
|
|
11
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Type, cast
|
|
12
|
+
|
|
13
|
+
from reflex.utils.exceptions import VarValueError
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from reflex.state import BaseState
|
|
17
|
+
|
|
18
|
+
from .base import Var
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
CellEmpty = object()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_cell_value(cell: CellType) -> Any:
|
|
25
|
+
"""Get the value of a cell object.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
cell: The cell object to get the value from. (func.__closure__ objects)
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
The value from the cell or CellEmpty if a ValueError is raised.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
return cell.cell_contents
|
|
35
|
+
except ValueError:
|
|
36
|
+
return CellEmpty
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ScanStatus(enum.Enum):
|
|
40
|
+
"""State of the dis instruction scanning loop."""
|
|
41
|
+
|
|
42
|
+
SCANNING = enum.auto()
|
|
43
|
+
GETTING_ATTR = enum.auto()
|
|
44
|
+
GETTING_STATE = enum.auto()
|
|
45
|
+
GETTING_VAR = enum.auto()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclasses.dataclass
|
|
49
|
+
class DependencyTracker:
|
|
50
|
+
"""State machine for identifying state attributes that are accessed by a function."""
|
|
51
|
+
|
|
52
|
+
func: FunctionType | CodeType = dataclasses.field()
|
|
53
|
+
state_cls: Type[BaseState] = dataclasses.field()
|
|
54
|
+
|
|
55
|
+
dependencies: dict[str, set[str]] = dataclasses.field(default_factory=dict)
|
|
56
|
+
|
|
57
|
+
scan_status: ScanStatus = dataclasses.field(default=ScanStatus.SCANNING)
|
|
58
|
+
top_of_stack: str | None = dataclasses.field(default=None)
|
|
59
|
+
|
|
60
|
+
tracked_locals: dict[str, Type[BaseState]] = dataclasses.field(default_factory=dict)
|
|
61
|
+
|
|
62
|
+
_getting_state_class: Type[BaseState] | None = dataclasses.field(default=None)
|
|
63
|
+
_getting_var_instructions: list[dis.Instruction] = dataclasses.field(
|
|
64
|
+
default_factory=list
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
INVALID_NAMES: ClassVar[list[str]] = ["parent_state", "substates", "get_substate"]
|
|
68
|
+
|
|
69
|
+
def __post_init__(self):
|
|
70
|
+
"""After initializing, populate the dependencies dict."""
|
|
71
|
+
with contextlib.suppress(AttributeError):
|
|
72
|
+
# unbox functools.partial
|
|
73
|
+
self.func = cast(FunctionType, self.func.func) # pyright: ignore[reportAttributeAccessIssue]
|
|
74
|
+
with contextlib.suppress(AttributeError):
|
|
75
|
+
# unbox EventHandler
|
|
76
|
+
self.func = cast(FunctionType, self.func.fn) # pyright: ignore[reportAttributeAccessIssue]
|
|
77
|
+
|
|
78
|
+
if isinstance(self.func, FunctionType):
|
|
79
|
+
with contextlib.suppress(AttributeError, IndexError):
|
|
80
|
+
# the first argument to the function is the name of "self" arg
|
|
81
|
+
self.tracked_locals[self.func.__code__.co_varnames[0]] = self.state_cls
|
|
82
|
+
|
|
83
|
+
self._populate_dependencies()
|
|
84
|
+
|
|
85
|
+
def _merge_deps(self, tracker: DependencyTracker) -> None:
|
|
86
|
+
"""Merge dependencies from another DependencyTracker.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
tracker: The DependencyTracker to merge dependencies from.
|
|
90
|
+
"""
|
|
91
|
+
for state_name, dep_name in tracker.dependencies.items():
|
|
92
|
+
self.dependencies.setdefault(state_name, set()).update(dep_name)
|
|
93
|
+
|
|
94
|
+
def load_attr_or_method(self, instruction: dis.Instruction) -> None:
|
|
95
|
+
"""Handle loading an attribute or method from the object on top of the stack.
|
|
96
|
+
|
|
97
|
+
This method directly tracks attributes and recursively merges
|
|
98
|
+
dependencies from analyzing the dependencies of any methods called.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
instruction: The dis instruction to process.
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
VarValueError: if the attribute is an disallowed name.
|
|
105
|
+
"""
|
|
106
|
+
from .base import ComputedVar
|
|
107
|
+
|
|
108
|
+
if instruction.argval in self.INVALID_NAMES:
|
|
109
|
+
raise VarValueError(
|
|
110
|
+
f"Cached var {self!s} cannot access arbitrary state via `{instruction.argval}`."
|
|
111
|
+
)
|
|
112
|
+
if instruction.argval == "get_state":
|
|
113
|
+
# Special case: arbitrary state access requested.
|
|
114
|
+
self.scan_status = ScanStatus.GETTING_STATE
|
|
115
|
+
return
|
|
116
|
+
if instruction.argval == "get_var_value":
|
|
117
|
+
# Special case: arbitrary var access requested.
|
|
118
|
+
self.scan_status = ScanStatus.GETTING_VAR
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
# Reset status back to SCANNING after attribute is accessed.
|
|
122
|
+
self.scan_status = ScanStatus.SCANNING
|
|
123
|
+
if not self.top_of_stack:
|
|
124
|
+
return
|
|
125
|
+
target_state = self.tracked_locals[self.top_of_stack]
|
|
126
|
+
try:
|
|
127
|
+
ref_obj = getattr(target_state, instruction.argval)
|
|
128
|
+
except AttributeError:
|
|
129
|
+
# Not found on this state class, maybe it is a dynamic attribute that will be picked up later.
|
|
130
|
+
ref_obj = None
|
|
131
|
+
|
|
132
|
+
if isinstance(ref_obj, property) and not isinstance(ref_obj, ComputedVar):
|
|
133
|
+
# recurse into property fget functions
|
|
134
|
+
ref_obj = ref_obj.fget
|
|
135
|
+
if callable(ref_obj):
|
|
136
|
+
# recurse into callable attributes
|
|
137
|
+
self._merge_deps(
|
|
138
|
+
type(self)(func=cast(FunctionType, ref_obj), state_cls=target_state)
|
|
139
|
+
)
|
|
140
|
+
elif (
|
|
141
|
+
instruction.argval in target_state.backend_vars
|
|
142
|
+
or instruction.argval in target_state.vars
|
|
143
|
+
):
|
|
144
|
+
# var access
|
|
145
|
+
self.dependencies.setdefault(target_state.get_full_name(), set()).add(
|
|
146
|
+
instruction.argval
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def _get_globals(self) -> dict[str, Any]:
|
|
150
|
+
"""Get the globals of the function.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
The var names and values in the globals of the function.
|
|
154
|
+
"""
|
|
155
|
+
if isinstance(self.func, CodeType):
|
|
156
|
+
return {}
|
|
157
|
+
return self.func.__globals__ # pyright: ignore[reportAttributeAccessIssue]
|
|
158
|
+
|
|
159
|
+
def _get_closure(self) -> dict[str, Any]:
|
|
160
|
+
"""Get the closure of the function, with unbound values omitted.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
The var names and values in the closure of the function.
|
|
164
|
+
"""
|
|
165
|
+
if isinstance(self.func, CodeType):
|
|
166
|
+
return {}
|
|
167
|
+
return {
|
|
168
|
+
var_name: get_cell_value(cell)
|
|
169
|
+
for var_name, cell in zip(
|
|
170
|
+
self.func.__code__.co_freevars, # pyright: ignore[reportAttributeAccessIssue]
|
|
171
|
+
self.func.__closure__ or (),
|
|
172
|
+
strict=False,
|
|
173
|
+
)
|
|
174
|
+
if get_cell_value(cell) is not CellEmpty
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
def handle_getting_state(self, instruction: dis.Instruction) -> None:
|
|
178
|
+
"""Handle bytecode analysis when `get_state` was called in the function.
|
|
179
|
+
|
|
180
|
+
If the wrapped function is getting an arbitrary state and saving it to a
|
|
181
|
+
local variable, this method associates the local variable name with the
|
|
182
|
+
state class in self.tracked_locals.
|
|
183
|
+
|
|
184
|
+
When an attribute/method is accessed on a tracked local, it will be
|
|
185
|
+
associated with this state.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
instruction: The dis instruction to process.
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
VarValueError: if the state class cannot be determined from the instruction.
|
|
192
|
+
"""
|
|
193
|
+
from reflex.state import BaseState
|
|
194
|
+
|
|
195
|
+
if instruction.opname == "LOAD_FAST":
|
|
196
|
+
raise VarValueError(
|
|
197
|
+
f"Dependency detection cannot identify get_state class from local var {instruction.argval}."
|
|
198
|
+
)
|
|
199
|
+
if isinstance(self.func, CodeType):
|
|
200
|
+
raise VarValueError(
|
|
201
|
+
"Dependency detection cannot identify get_state class from a code object."
|
|
202
|
+
)
|
|
203
|
+
if instruction.opname == "LOAD_GLOBAL":
|
|
204
|
+
# Special case: referencing state class from global scope.
|
|
205
|
+
try:
|
|
206
|
+
self._getting_state_class = self._get_globals()[instruction.argval]
|
|
207
|
+
except (ValueError, KeyError) as ve:
|
|
208
|
+
raise VarValueError(
|
|
209
|
+
f"Cached var {self!s} cannot access arbitrary state `{instruction.argval}`, not found in globals."
|
|
210
|
+
) from ve
|
|
211
|
+
elif instruction.opname == "LOAD_DEREF":
|
|
212
|
+
# Special case: referencing state class from closure.
|
|
213
|
+
try:
|
|
214
|
+
self._getting_state_class = self._get_closure()[instruction.argval]
|
|
215
|
+
except (ValueError, KeyError) as ve:
|
|
216
|
+
raise VarValueError(
|
|
217
|
+
f"Cached var {self!s} cannot access arbitrary state `{instruction.argval}`, is it defined yet?"
|
|
218
|
+
) from ve
|
|
219
|
+
elif instruction.opname == "STORE_FAST":
|
|
220
|
+
# Storing the result of get_state in a local variable.
|
|
221
|
+
if not isinstance(self._getting_state_class, type) or not issubclass(
|
|
222
|
+
self._getting_state_class, BaseState
|
|
223
|
+
):
|
|
224
|
+
raise VarValueError(
|
|
225
|
+
f"Cached var {self!s} cannot determine dependencies in fetched state `{instruction.argval}`."
|
|
226
|
+
)
|
|
227
|
+
self.tracked_locals[instruction.argval] = self._getting_state_class
|
|
228
|
+
self.scan_status = ScanStatus.SCANNING
|
|
229
|
+
self._getting_state_class = None
|
|
230
|
+
|
|
231
|
+
def _eval_var(self) -> Var:
|
|
232
|
+
"""Evaluate instructions from the wrapped function to get the Var object.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
The Var object.
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
VarValueError: if the source code for the var cannot be determined.
|
|
239
|
+
"""
|
|
240
|
+
# Get the original source code and eval it to get the Var.
|
|
241
|
+
module = inspect.getmodule(self.func)
|
|
242
|
+
positions0 = self._getting_var_instructions[0].positions
|
|
243
|
+
positions1 = self._getting_var_instructions[-1].positions
|
|
244
|
+
if module is None or positions0 is None or positions1 is None:
|
|
245
|
+
raise VarValueError(
|
|
246
|
+
f"Cannot determine the source code for the var in {self.func!r}."
|
|
247
|
+
)
|
|
248
|
+
start_line = positions0.lineno
|
|
249
|
+
start_column = positions0.col_offset
|
|
250
|
+
end_line = positions1.end_lineno
|
|
251
|
+
end_column = positions1.end_col_offset
|
|
252
|
+
if (
|
|
253
|
+
start_line is None
|
|
254
|
+
or start_column is None
|
|
255
|
+
or end_line is None
|
|
256
|
+
or end_column is None
|
|
257
|
+
):
|
|
258
|
+
raise VarValueError(
|
|
259
|
+
f"Cannot determine the source code for the var in {self.func!r}."
|
|
260
|
+
)
|
|
261
|
+
source = inspect.getsource(module).splitlines(True)[start_line - 1 : end_line]
|
|
262
|
+
# Create a python source string snippet.
|
|
263
|
+
if len(source) > 1:
|
|
264
|
+
snipped_source = "".join(
|
|
265
|
+
[
|
|
266
|
+
*source[0][start_column:],
|
|
267
|
+
*(source[1:-2] if len(source) > 2 else []),
|
|
268
|
+
*source[-1][: end_column - 1],
|
|
269
|
+
]
|
|
270
|
+
)
|
|
271
|
+
else:
|
|
272
|
+
snipped_source = source[0][start_column : end_column - 1]
|
|
273
|
+
# Evaluate the string in the context of the function's globals and closure.
|
|
274
|
+
return eval(f"({snipped_source})", self._get_globals(), self._get_closure())
|
|
275
|
+
|
|
276
|
+
def handle_getting_var(self, instruction: dis.Instruction) -> None:
|
|
277
|
+
"""Handle bytecode analysis when `get_var_value` was called in the function.
|
|
278
|
+
|
|
279
|
+
This only really works if the expression passed to `get_var_value` is
|
|
280
|
+
evaluable in the function's global scope or closure, so getting the var
|
|
281
|
+
value from a var saved in a local variable or in the class instance is
|
|
282
|
+
not possible.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
instruction: The dis instruction to process.
|
|
286
|
+
|
|
287
|
+
Raises:
|
|
288
|
+
VarValueError: if the source code for the var cannot be determined.
|
|
289
|
+
"""
|
|
290
|
+
if instruction.opname == "CALL" and self._getting_var_instructions:
|
|
291
|
+
if self._getting_var_instructions:
|
|
292
|
+
the_var = self._eval_var()
|
|
293
|
+
the_var_data = the_var._get_all_var_data()
|
|
294
|
+
if the_var_data is None:
|
|
295
|
+
raise VarValueError(
|
|
296
|
+
f"Cannot determine the source code for the var in {self.func!r}."
|
|
297
|
+
)
|
|
298
|
+
self.dependencies.setdefault(the_var_data.state, set()).add(
|
|
299
|
+
the_var_data.field_name
|
|
300
|
+
)
|
|
301
|
+
self._getting_var_instructions.clear()
|
|
302
|
+
self.scan_status = ScanStatus.SCANNING
|
|
303
|
+
else:
|
|
304
|
+
self._getting_var_instructions.append(instruction)
|
|
305
|
+
|
|
306
|
+
def _populate_dependencies(self) -> None:
|
|
307
|
+
"""Update self.dependencies based on the disassembly of self.func.
|
|
308
|
+
|
|
309
|
+
Save references to attributes accessed on "self" or other fetched states.
|
|
310
|
+
|
|
311
|
+
Recursively called when the function makes a method call on "self" or
|
|
312
|
+
define comprehensions or nested functions that may reference "self".
|
|
313
|
+
"""
|
|
314
|
+
for instruction in dis.get_instructions(self.func):
|
|
315
|
+
if self.scan_status == ScanStatus.GETTING_STATE:
|
|
316
|
+
self.handle_getting_state(instruction)
|
|
317
|
+
elif self.scan_status == ScanStatus.GETTING_VAR:
|
|
318
|
+
self.handle_getting_var(instruction)
|
|
319
|
+
elif (
|
|
320
|
+
instruction.opname in ("LOAD_FAST", "LOAD_DEREF")
|
|
321
|
+
and instruction.argval in self.tracked_locals
|
|
322
|
+
):
|
|
323
|
+
# bytecode loaded the class instance to the top of stack, next load instruction
|
|
324
|
+
# is referencing an attribute on self
|
|
325
|
+
self.top_of_stack = instruction.argval
|
|
326
|
+
self.scan_status = ScanStatus.GETTING_ATTR
|
|
327
|
+
elif self.scan_status == ScanStatus.GETTING_ATTR and instruction.opname in (
|
|
328
|
+
"LOAD_ATTR",
|
|
329
|
+
"LOAD_METHOD",
|
|
330
|
+
):
|
|
331
|
+
self.load_attr_or_method(instruction)
|
|
332
|
+
self.top_of_stack = None
|
|
333
|
+
elif instruction.opname == "LOAD_CONST" and isinstance(
|
|
334
|
+
instruction.argval, CodeType
|
|
335
|
+
):
|
|
336
|
+
# recurse into nested functions / comprehensions, which can reference
|
|
337
|
+
# instance attributes from the outer scope
|
|
338
|
+
self._merge_deps(
|
|
339
|
+
type(self)(
|
|
340
|
+
func=instruction.argval,
|
|
341
|
+
state_cls=self.state_cls,
|
|
342
|
+
tracked_locals=self.tracked_locals,
|
|
343
|
+
)
|
|
344
|
+
)
|
reflex/vars/function.py
CHANGED
|
@@ -100,7 +100,7 @@ class FunctionVar(Var[CALLABLE_TYPE], default_type=ReflexCallable[Any, Any]):
|
|
|
100
100
|
@overload
|
|
101
101
|
def partial(self, *args: Var | Any) -> FunctionVar: ...
|
|
102
102
|
|
|
103
|
-
def partial(self, *args: Var | Any) -> FunctionVar: #
|
|
103
|
+
def partial(self, *args: Var | Any) -> FunctionVar: # pyright: ignore [reportInconsistentOverload]
|
|
104
104
|
"""Partially apply the function with the given arguments.
|
|
105
105
|
|
|
106
106
|
Args:
|
|
@@ -174,7 +174,7 @@ class FunctionVar(Var[CALLABLE_TYPE], default_type=ReflexCallable[Any, Any]):
|
|
|
174
174
|
@overload
|
|
175
175
|
def call(self, *args: Var | Any) -> Var: ...
|
|
176
176
|
|
|
177
|
-
def call(self, *args: Var | Any) -> Var: #
|
|
177
|
+
def call(self, *args: Var | Any) -> Var: # pyright: ignore [reportInconsistentOverload]
|
|
178
178
|
"""Call the function with the given arguments.
|
|
179
179
|
|
|
180
180
|
Args:
|
|
@@ -210,6 +210,7 @@ class FunctionStringVar(FunctionVar[CALLABLE_TYPE]):
|
|
|
210
210
|
|
|
211
211
|
Args:
|
|
212
212
|
func: The function to call.
|
|
213
|
+
_var_type: The type of the Var.
|
|
213
214
|
_var_data: Additional hooks and imports associated with the Var.
|
|
214
215
|
|
|
215
216
|
Returns:
|
|
@@ -225,7 +226,7 @@ class FunctionStringVar(FunctionVar[CALLABLE_TYPE]):
|
|
|
225
226
|
@dataclasses.dataclass(
|
|
226
227
|
eq=False,
|
|
227
228
|
frozen=True,
|
|
228
|
-
|
|
229
|
+
slots=True,
|
|
229
230
|
)
|
|
230
231
|
class VarOperationCall(Generic[P, R], CachedVarOperation, Var[R]):
|
|
231
232
|
"""Base class for immutable vars that are the result of a function call."""
|
|
@@ -268,6 +269,7 @@ class VarOperationCall(Generic[P, R], CachedVarOperation, Var[R]):
|
|
|
268
269
|
Args:
|
|
269
270
|
func: The function to call.
|
|
270
271
|
*args: The arguments to call the function with.
|
|
272
|
+
_var_type: The type of the Var.
|
|
271
273
|
_var_data: Additional hooks and imports associated with the Var.
|
|
272
274
|
|
|
273
275
|
Returns:
|
|
@@ -348,7 +350,7 @@ def format_args_function_operation(
|
|
|
348
350
|
@dataclasses.dataclass(
|
|
349
351
|
eq=False,
|
|
350
352
|
frozen=True,
|
|
351
|
-
|
|
353
|
+
slots=True,
|
|
352
354
|
)
|
|
353
355
|
class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
|
|
354
356
|
"""Base class for immutable function defined via arguments and return expression."""
|
|
@@ -385,11 +387,13 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
|
|
|
385
387
|
return_expr: The return expression of the function.
|
|
386
388
|
rest: The name of the rest argument.
|
|
387
389
|
explicit_return: Whether to use explicit return syntax.
|
|
390
|
+
_var_type: The type of the Var.
|
|
388
391
|
_var_data: Additional hooks and imports associated with the Var.
|
|
389
392
|
|
|
390
393
|
Returns:
|
|
391
394
|
The function var.
|
|
392
395
|
"""
|
|
396
|
+
return_expr = Var.create(return_expr)
|
|
393
397
|
return cls(
|
|
394
398
|
_js_expr="",
|
|
395
399
|
_var_type=_var_type,
|
|
@@ -403,7 +407,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
|
|
|
403
407
|
@dataclasses.dataclass(
|
|
404
408
|
eq=False,
|
|
405
409
|
frozen=True,
|
|
406
|
-
|
|
410
|
+
slots=True,
|
|
407
411
|
)
|
|
408
412
|
class ArgsFunctionOperationBuilder(CachedVarOperation, BuilderFunctionVar):
|
|
409
413
|
"""Base class for immutable function defined via arguments and return expression with the builder pattern."""
|
|
@@ -440,11 +444,13 @@ class ArgsFunctionOperationBuilder(CachedVarOperation, BuilderFunctionVar):
|
|
|
440
444
|
return_expr: The return expression of the function.
|
|
441
445
|
rest: The name of the rest argument.
|
|
442
446
|
explicit_return: Whether to use explicit return syntax.
|
|
447
|
+
_var_type: The type of the Var.
|
|
443
448
|
_var_data: Additional hooks and imports associated with the Var.
|
|
444
449
|
|
|
445
450
|
Returns:
|
|
446
451
|
The function var.
|
|
447
452
|
"""
|
|
453
|
+
return_expr = Var.create(return_expr)
|
|
448
454
|
return cls(
|
|
449
455
|
_js_expr="",
|
|
450
456
|
_var_type=_var_type,
|