reflex 0.6.8a2__py3-none-any.whl → 0.7.0a2__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.

Files changed (246) hide show
  1. reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +1 -1
  2. reflex/.templates/jinja/web/pages/_app.js.jinja2 +7 -7
  3. reflex/.templates/jinja/web/pages/utils.js.jinja2 +3 -3
  4. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +1 -4
  5. reflex/.templates/web/utils/state.js +65 -36
  6. reflex/__init__.py +4 -17
  7. reflex/__init__.pyi +1 -2
  8. reflex/app.py +249 -116
  9. reflex/app_mixins/lifespan.py +9 -9
  10. reflex/app_mixins/middleware.py +6 -6
  11. reflex/app_module_for_backend.py +3 -7
  12. reflex/base.py +7 -7
  13. reflex/compiler/compiler.py +8 -0
  14. reflex/compiler/utils.py +35 -6
  15. reflex/components/base/app_wrap.pyi +16 -16
  16. reflex/components/base/bare.py +1 -1
  17. reflex/components/base/body.pyi +16 -16
  18. reflex/components/base/document.pyi +76 -76
  19. reflex/components/base/error_boundary.py +2 -1
  20. reflex/components/base/error_boundary.pyi +19 -22
  21. reflex/components/base/fragment.pyi +16 -16
  22. reflex/components/base/head.pyi +31 -31
  23. reflex/components/base/link.pyi +31 -31
  24. reflex/components/base/meta.py +2 -2
  25. reflex/components/base/meta.pyi +61 -61
  26. reflex/components/base/script.pyi +19 -19
  27. reflex/components/base/strict_mode.py +10 -0
  28. reflex/components/base/strict_mode.pyi +57 -0
  29. reflex/components/component.py +38 -77
  30. reflex/components/core/banner.py +159 -4
  31. reflex/components/core/banner.pyi +162 -76
  32. reflex/components/core/breakpoints.py +3 -1
  33. reflex/components/core/client_side_routing.py +1 -1
  34. reflex/components/core/client_side_routing.pyi +32 -32
  35. reflex/components/core/clipboard.pyi +17 -20
  36. reflex/components/core/cond.py +9 -10
  37. reflex/components/core/debounce.py +1 -1
  38. reflex/components/core/debounce.pyi +17 -17
  39. reflex/components/core/foreach.py +28 -3
  40. reflex/components/core/html.py +1 -1
  41. reflex/components/core/html.pyi +16 -16
  42. reflex/components/core/match.py +5 -5
  43. reflex/components/core/sticky.py +160 -0
  44. reflex/components/core/sticky.pyi +449 -0
  45. reflex/components/core/upload.py +2 -2
  46. reflex/components/core/upload.pyi +80 -88
  47. reflex/components/datadisplay/code.py +5 -14
  48. reflex/components/datadisplay/code.pyi +31 -31
  49. reflex/components/datadisplay/dataeditor.py +7 -4
  50. reflex/components/datadisplay/dataeditor.pyi +40 -54
  51. reflex/components/datadisplay/logo.py +13 -8
  52. reflex/components/datadisplay/shiki_code_block.py +14 -9
  53. reflex/components/datadisplay/shiki_code_block.pyi +46 -46
  54. reflex/components/dynamic.py +22 -3
  55. reflex/components/el/constants/reflex.py +1 -1
  56. reflex/components/el/element.py +1 -1
  57. reflex/components/el/element.pyi +16 -16
  58. reflex/components/el/elements/base.pyi +16 -16
  59. reflex/components/el/elements/forms.py +4 -4
  60. reflex/components/el/elements/forms.pyi +224 -258
  61. reflex/components/el/elements/inline.pyi +421 -421
  62. reflex/components/el/elements/media.pyi +376 -376
  63. reflex/components/el/elements/metadata.pyi +91 -91
  64. reflex/components/el/elements/other.pyi +106 -106
  65. reflex/components/el/elements/scripts.pyi +46 -46
  66. reflex/components/el/elements/sectioning.pyi +226 -226
  67. reflex/components/el/elements/tables.pyi +151 -151
  68. reflex/components/el/elements/typography.pyi +226 -226
  69. reflex/components/gridjs/datatable.pyi +31 -31
  70. reflex/components/lucide/icon.py +46 -8
  71. reflex/components/lucide/icon.pyi +85 -31
  72. reflex/components/markdown/markdown.py +10 -8
  73. reflex/components/markdown/markdown.pyi +16 -16
  74. reflex/components/moment/moment.py +2 -2
  75. reflex/components/moment/moment.pyi +17 -19
  76. reflex/components/next/base.pyi +16 -16
  77. reflex/components/next/image.py +16 -4
  78. reflex/components/next/image.pyi +22 -20
  79. reflex/components/next/link.py +1 -1
  80. reflex/components/next/link.pyi +16 -16
  81. reflex/components/next/video.pyi +16 -16
  82. reflex/components/plotly/plotly.py +5 -5
  83. reflex/components/plotly/plotly.pyi +34 -44
  84. reflex/components/props.py +3 -3
  85. reflex/components/radix/__init__.pyi +1 -1
  86. reflex/components/radix/primitives/accordion.py +9 -5
  87. reflex/components/radix/primitives/accordion.pyi +110 -108
  88. reflex/components/radix/primitives/base.pyi +31 -31
  89. reflex/components/radix/primitives/drawer.py +5 -2
  90. reflex/components/radix/primitives/drawer.pyi +179 -187
  91. reflex/components/radix/primitives/form.pyi +160 -172
  92. reflex/components/radix/primitives/progress.py +1 -1
  93. reflex/components/radix/primitives/progress.pyi +76 -76
  94. reflex/components/radix/primitives/slider.py +1 -1
  95. reflex/components/radix/primitives/slider.pyi +78 -82
  96. reflex/components/radix/themes/base.pyi +121 -121
  97. reflex/components/radix/themes/color_mode.py +11 -9
  98. reflex/components/radix/themes/color_mode.pyi +47 -49
  99. reflex/components/radix/themes/components/alert_dialog.py +3 -0
  100. reflex/components/radix/themes/components/alert_dialog.pyi +110 -112
  101. reflex/components/radix/themes/components/aspect_ratio.pyi +16 -16
  102. reflex/components/radix/themes/components/avatar.pyi +16 -16
  103. reflex/components/radix/themes/components/badge.pyi +16 -16
  104. reflex/components/radix/themes/components/button.pyi +16 -16
  105. reflex/components/radix/themes/components/callout.pyi +76 -76
  106. reflex/components/radix/themes/components/card.py +1 -1
  107. reflex/components/radix/themes/components/card.pyi +17 -17
  108. reflex/components/radix/themes/components/checkbox.pyi +49 -55
  109. reflex/components/radix/themes/components/checkbox_cards.pyi +31 -31
  110. reflex/components/radix/themes/components/checkbox_group.pyi +31 -31
  111. reflex/components/radix/themes/components/context_menu.py +5 -0
  112. reflex/components/radix/themes/components/context_menu.pyi +149 -155
  113. reflex/components/radix/themes/components/data_list.pyi +61 -61
  114. reflex/components/radix/themes/components/dialog.py +3 -0
  115. reflex/components/radix/themes/components/dialog.pyi +113 -117
  116. reflex/components/radix/themes/components/dropdown_menu.py +5 -0
  117. reflex/components/radix/themes/components/dropdown_menu.pyi +133 -137
  118. reflex/components/radix/themes/components/hover_card.py +3 -0
  119. reflex/components/radix/themes/components/hover_card.pyi +63 -67
  120. reflex/components/radix/themes/components/icon_button.py +2 -2
  121. reflex/components/radix/themes/components/icon_button.pyi +17 -16
  122. reflex/components/radix/themes/components/inset.pyi +16 -16
  123. reflex/components/radix/themes/components/popover.py +3 -0
  124. reflex/components/radix/themes/components/popover.pyi +68 -70
  125. reflex/components/radix/themes/components/progress.pyi +16 -16
  126. reflex/components/radix/themes/components/radio.pyi +16 -16
  127. reflex/components/radix/themes/components/radio_cards.py +2 -0
  128. reflex/components/radix/themes/components/radio_cards.pyi +32 -34
  129. reflex/components/radix/themes/components/radio_group.py +1 -1
  130. reflex/components/radix/themes/components/radio_group.pyi +62 -64
  131. reflex/components/radix/themes/components/scroll_area.pyi +16 -16
  132. reflex/components/radix/themes/components/segmented_control.pyi +32 -35
  133. reflex/components/radix/themes/components/select.py +4 -0
  134. reflex/components/radix/themes/components/select.pyi +145 -157
  135. reflex/components/radix/themes/components/separator.pyi +16 -16
  136. reflex/components/radix/themes/components/skeleton.py +3 -0
  137. reflex/components/radix/themes/components/skeleton.pyi +16 -16
  138. reflex/components/radix/themes/components/slider.pyi +22 -28
  139. reflex/components/radix/themes/components/spinner.pyi +16 -16
  140. reflex/components/radix/themes/components/switch.pyi +17 -19
  141. reflex/components/radix/themes/components/table.pyi +106 -106
  142. reflex/components/radix/themes/components/tabs.py +3 -0
  143. reflex/components/radix/themes/components/tabs.pyi +78 -82
  144. reflex/components/radix/themes/components/text_area.py +12 -0
  145. reflex/components/radix/themes/components/text_area.pyi +21 -33
  146. reflex/components/radix/themes/components/text_field.py +1 -1
  147. reflex/components/radix/themes/components/text_field.pyi +52 -80
  148. reflex/components/radix/themes/components/tooltip.py +6 -1
  149. reflex/components/radix/themes/components/tooltip.pyi +20 -21
  150. reflex/components/radix/themes/layout/__init__.pyi +1 -1
  151. reflex/components/radix/themes/layout/base.pyi +16 -16
  152. reflex/components/radix/themes/layout/box.pyi +16 -16
  153. reflex/components/radix/themes/layout/center.pyi +16 -16
  154. reflex/components/radix/themes/layout/container.pyi +16 -16
  155. reflex/components/radix/themes/layout/flex.pyi +16 -16
  156. reflex/components/radix/themes/layout/grid.pyi +16 -16
  157. reflex/components/radix/themes/layout/list.py +2 -2
  158. reflex/components/radix/themes/layout/list.pyi +76 -76
  159. reflex/components/radix/themes/layout/section.pyi +16 -16
  160. reflex/components/radix/themes/layout/spacer.pyi +16 -16
  161. reflex/components/radix/themes/layout/stack.py +2 -2
  162. reflex/components/radix/themes/layout/stack.pyi +46 -46
  163. reflex/components/radix/themes/typography/blockquote.pyi +16 -16
  164. reflex/components/radix/themes/typography/code.pyi +16 -16
  165. reflex/components/radix/themes/typography/heading.pyi +16 -16
  166. reflex/components/radix/themes/typography/link.py +1 -1
  167. reflex/components/radix/themes/typography/link.pyi +16 -16
  168. reflex/components/radix/themes/typography/text.py +2 -2
  169. reflex/components/radix/themes/typography/text.pyi +106 -106
  170. reflex/components/react_player/audio.pyi +33 -39
  171. reflex/components/react_player/react_player.py +1 -1
  172. reflex/components/react_player/react_player.pyi +32 -38
  173. reflex/components/react_player/video.pyi +33 -39
  174. reflex/components/recharts/__init__.py +2 -0
  175. reflex/components/recharts/__init__.pyi +2 -0
  176. reflex/components/recharts/cartesian.pyi +282 -282
  177. reflex/components/recharts/charts.py +15 -15
  178. reflex/components/recharts/charts.pyi +164 -164
  179. reflex/components/recharts/general.py +19 -4
  180. reflex/components/recharts/general.pyi +132 -81
  181. reflex/components/recharts/polar.py +2 -2
  182. reflex/components/recharts/polar.pyi +55 -55
  183. reflex/components/recharts/recharts.py +4 -4
  184. reflex/components/recharts/recharts.pyi +31 -31
  185. reflex/components/sonner/toast.py +15 -13
  186. reflex/components/sonner/toast.pyi +22 -22
  187. reflex/components/suneditor/editor.py +6 -4
  188. reflex/components/suneditor/editor.pyi +26 -40
  189. reflex/components/tags/iter_tag.py +3 -3
  190. reflex/components/tags/tag.py +25 -3
  191. reflex/config.py +48 -15
  192. reflex/constants/__init__.py +1 -0
  193. reflex/constants/base.py +4 -1
  194. reflex/constants/compiler.py +5 -2
  195. reflex/constants/config.py +8 -1
  196. reflex/constants/installer.py +9 -9
  197. reflex/constants/style.py +1 -1
  198. reflex/custom_components/custom_components.py +9 -7
  199. reflex/event.py +215 -208
  200. reflex/experimental/__init__.py +19 -11
  201. reflex/experimental/client_state.py +53 -28
  202. reflex/experimental/hooks.py +5 -5
  203. reflex/experimental/layout.py +8 -5
  204. reflex/experimental/layout.pyi +79 -83
  205. reflex/experimental/misc.py +3 -3
  206. reflex/istate/wrappers.py +1 -1
  207. reflex/middleware/hydrate_middleware.py +2 -2
  208. reflex/model.py +11 -6
  209. reflex/page.py +5 -5
  210. reflex/reflex.py +90 -19
  211. reflex/route.py +1 -1
  212. reflex/state.py +358 -401
  213. reflex/style.py +27 -3
  214. reflex/testing.py +29 -23
  215. reflex/utils/build.py +6 -2
  216. reflex/utils/codespaces.py +1 -4
  217. reflex/utils/compat.py +6 -5
  218. reflex/utils/console.py +52 -16
  219. reflex/utils/exceptions.py +89 -26
  220. reflex/utils/exec.py +69 -74
  221. reflex/utils/export.py +6 -1
  222. reflex/utils/format.py +8 -40
  223. reflex/utils/imports.py +2 -2
  224. reflex/utils/lazy_loader.py +7 -1
  225. reflex/utils/path_ops.py +28 -14
  226. reflex/utils/prerequisites.py +326 -67
  227. reflex/utils/processes.py +45 -32
  228. reflex/utils/pyi_generator.py +39 -33
  229. reflex/utils/registry.py +4 -4
  230. reflex/utils/serializers.py +1 -1
  231. reflex/utils/telemetry.py +5 -4
  232. reflex/utils/types.py +42 -18
  233. reflex/vars/base.py +656 -333
  234. reflex/vars/datetime.py +6 -7
  235. reflex/vars/dep_tracking.py +344 -0
  236. reflex/vars/function.py +11 -5
  237. reflex/vars/number.py +31 -43
  238. reflex/vars/object.py +63 -62
  239. reflex/vars/sequence.py +79 -67
  240. {reflex-0.6.8a2.dist-info → reflex-0.7.0a2.dist-info}/METADATA +7 -8
  241. reflex-0.7.0a2.dist-info/RECORD +401 -0
  242. {reflex-0.6.8a2.dist-info → reflex-0.7.0a2.dist-info}/WHEEL +1 -1
  243. reflex/experimental/assets.py +0 -37
  244. reflex-0.6.8a2.dist-info/RECORD +0 -397
  245. {reflex-0.6.8a2.dist-info → reflex-0.7.0a2.dist-info}/LICENSE +0 -0
  246. {reflex-0.6.8a2.dist-info → reflex-0.7.0a2.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} { '<' if strict else '<='} {rhs})",
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
- **{"slots": True} if sys.version_info >= (3, 10) else {},
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: # type: ignore
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: # type: ignore
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
- **{"slots": True} if sys.version_info >= (3, 10) else {},
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
- **{"slots": True} if sys.version_info >= (3, 10) else {},
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
- **{"slots": True} if sys.version_info >= (3, 10) else {},
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,