reflex 0.6.8a2__py3-none-any.whl → 0.7.0__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 (247) 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 +286 -129
  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 +57 -18
  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 +134 -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/__init__.py +29 -2
  83. reflex/components/plotly/plotly.py +240 -5
  84. reflex/components/plotly/plotly.pyi +799 -44
  85. reflex/components/props.py +3 -3
  86. reflex/components/radix/__init__.pyi +1 -1
  87. reflex/components/radix/primitives/accordion.py +9 -5
  88. reflex/components/radix/primitives/accordion.pyi +110 -108
  89. reflex/components/radix/primitives/base.pyi +31 -31
  90. reflex/components/radix/primitives/drawer.py +5 -2
  91. reflex/components/radix/primitives/drawer.pyi +179 -187
  92. reflex/components/radix/primitives/form.pyi +160 -172
  93. reflex/components/radix/primitives/progress.py +1 -1
  94. reflex/components/radix/primitives/progress.pyi +76 -76
  95. reflex/components/radix/primitives/slider.py +1 -1
  96. reflex/components/radix/primitives/slider.pyi +78 -82
  97. reflex/components/radix/themes/base.pyi +121 -121
  98. reflex/components/radix/themes/color_mode.py +11 -9
  99. reflex/components/radix/themes/color_mode.pyi +47 -49
  100. reflex/components/radix/themes/components/alert_dialog.py +3 -0
  101. reflex/components/radix/themes/components/alert_dialog.pyi +110 -112
  102. reflex/components/radix/themes/components/aspect_ratio.pyi +16 -16
  103. reflex/components/radix/themes/components/avatar.pyi +16 -16
  104. reflex/components/radix/themes/components/badge.pyi +16 -16
  105. reflex/components/radix/themes/components/button.pyi +16 -16
  106. reflex/components/radix/themes/components/callout.pyi +76 -76
  107. reflex/components/radix/themes/components/card.py +1 -1
  108. reflex/components/radix/themes/components/card.pyi +17 -17
  109. reflex/components/radix/themes/components/checkbox.pyi +49 -55
  110. reflex/components/radix/themes/components/checkbox_cards.pyi +31 -31
  111. reflex/components/radix/themes/components/checkbox_group.pyi +31 -31
  112. reflex/components/radix/themes/components/context_menu.py +5 -0
  113. reflex/components/radix/themes/components/context_menu.pyi +149 -155
  114. reflex/components/radix/themes/components/data_list.pyi +61 -61
  115. reflex/components/radix/themes/components/dialog.py +3 -0
  116. reflex/components/radix/themes/components/dialog.pyi +113 -117
  117. reflex/components/radix/themes/components/dropdown_menu.py +5 -0
  118. reflex/components/radix/themes/components/dropdown_menu.pyi +133 -137
  119. reflex/components/radix/themes/components/hover_card.py +3 -0
  120. reflex/components/radix/themes/components/hover_card.pyi +63 -67
  121. reflex/components/radix/themes/components/icon_button.py +2 -2
  122. reflex/components/radix/themes/components/icon_button.pyi +17 -16
  123. reflex/components/radix/themes/components/inset.pyi +16 -16
  124. reflex/components/radix/themes/components/popover.py +3 -0
  125. reflex/components/radix/themes/components/popover.pyi +68 -70
  126. reflex/components/radix/themes/components/progress.pyi +16 -16
  127. reflex/components/radix/themes/components/radio.pyi +16 -16
  128. reflex/components/radix/themes/components/radio_cards.py +2 -0
  129. reflex/components/radix/themes/components/radio_cards.pyi +32 -34
  130. reflex/components/radix/themes/components/radio_group.py +1 -1
  131. reflex/components/radix/themes/components/radio_group.pyi +62 -64
  132. reflex/components/radix/themes/components/scroll_area.pyi +16 -16
  133. reflex/components/radix/themes/components/segmented_control.pyi +32 -35
  134. reflex/components/radix/themes/components/select.py +4 -0
  135. reflex/components/radix/themes/components/select.pyi +145 -157
  136. reflex/components/radix/themes/components/separator.pyi +16 -16
  137. reflex/components/radix/themes/components/skeleton.py +3 -0
  138. reflex/components/radix/themes/components/skeleton.pyi +16 -16
  139. reflex/components/radix/themes/components/slider.pyi +22 -28
  140. reflex/components/radix/themes/components/spinner.pyi +16 -16
  141. reflex/components/radix/themes/components/switch.pyi +17 -19
  142. reflex/components/radix/themes/components/table.pyi +106 -106
  143. reflex/components/radix/themes/components/tabs.py +3 -0
  144. reflex/components/radix/themes/components/tabs.pyi +78 -82
  145. reflex/components/radix/themes/components/text_area.py +12 -0
  146. reflex/components/radix/themes/components/text_area.pyi +21 -33
  147. reflex/components/radix/themes/components/text_field.py +1 -1
  148. reflex/components/radix/themes/components/text_field.pyi +52 -80
  149. reflex/components/radix/themes/components/tooltip.py +6 -1
  150. reflex/components/radix/themes/components/tooltip.pyi +20 -21
  151. reflex/components/radix/themes/layout/__init__.pyi +1 -1
  152. reflex/components/radix/themes/layout/base.pyi +16 -16
  153. reflex/components/radix/themes/layout/box.pyi +16 -16
  154. reflex/components/radix/themes/layout/center.pyi +16 -16
  155. reflex/components/radix/themes/layout/container.pyi +16 -16
  156. reflex/components/radix/themes/layout/flex.pyi +16 -16
  157. reflex/components/radix/themes/layout/grid.pyi +16 -16
  158. reflex/components/radix/themes/layout/list.py +2 -2
  159. reflex/components/radix/themes/layout/list.pyi +76 -76
  160. reflex/components/radix/themes/layout/section.pyi +16 -16
  161. reflex/components/radix/themes/layout/spacer.pyi +16 -16
  162. reflex/components/radix/themes/layout/stack.py +2 -2
  163. reflex/components/radix/themes/layout/stack.pyi +46 -46
  164. reflex/components/radix/themes/typography/blockquote.pyi +16 -16
  165. reflex/components/radix/themes/typography/code.pyi +16 -16
  166. reflex/components/radix/themes/typography/heading.pyi +16 -16
  167. reflex/components/radix/themes/typography/link.py +1 -1
  168. reflex/components/radix/themes/typography/link.pyi +16 -16
  169. reflex/components/radix/themes/typography/text.py +2 -2
  170. reflex/components/radix/themes/typography/text.pyi +106 -106
  171. reflex/components/react_player/audio.pyi +33 -39
  172. reflex/components/react_player/react_player.py +1 -1
  173. reflex/components/react_player/react_player.pyi +32 -38
  174. reflex/components/react_player/video.pyi +33 -39
  175. reflex/components/recharts/__init__.py +2 -0
  176. reflex/components/recharts/__init__.pyi +2 -0
  177. reflex/components/recharts/cartesian.pyi +282 -282
  178. reflex/components/recharts/charts.py +15 -15
  179. reflex/components/recharts/charts.pyi +164 -164
  180. reflex/components/recharts/general.py +19 -4
  181. reflex/components/recharts/general.pyi +132 -81
  182. reflex/components/recharts/polar.py +2 -2
  183. reflex/components/recharts/polar.pyi +55 -55
  184. reflex/components/recharts/recharts.py +4 -4
  185. reflex/components/recharts/recharts.pyi +31 -31
  186. reflex/components/sonner/toast.py +15 -13
  187. reflex/components/sonner/toast.pyi +22 -22
  188. reflex/components/suneditor/editor.py +6 -4
  189. reflex/components/suneditor/editor.pyi +26 -40
  190. reflex/components/tags/iter_tag.py +3 -3
  191. reflex/components/tags/tag.py +25 -3
  192. reflex/config.py +48 -15
  193. reflex/constants/__init__.py +1 -0
  194. reflex/constants/base.py +4 -1
  195. reflex/constants/compiler.py +5 -2
  196. reflex/constants/config.py +8 -1
  197. reflex/constants/installer.py +9 -9
  198. reflex/constants/style.py +1 -1
  199. reflex/custom_components/custom_components.py +18 -10
  200. reflex/event.py +221 -231
  201. reflex/experimental/__init__.py +19 -11
  202. reflex/experimental/client_state.py +53 -28
  203. reflex/experimental/hooks.py +5 -5
  204. reflex/experimental/layout.py +8 -5
  205. reflex/experimental/layout.pyi +79 -83
  206. reflex/experimental/misc.py +3 -3
  207. reflex/istate/wrappers.py +1 -1
  208. reflex/middleware/hydrate_middleware.py +2 -2
  209. reflex/model.py +11 -6
  210. reflex/page.py +5 -5
  211. reflex/reflex.py +104 -26
  212. reflex/route.py +1 -1
  213. reflex/state.py +358 -401
  214. reflex/style.py +27 -3
  215. reflex/testing.py +29 -23
  216. reflex/utils/build.py +6 -2
  217. reflex/utils/codespaces.py +1 -4
  218. reflex/utils/compat.py +6 -5
  219. reflex/utils/console.py +71 -16
  220. reflex/utils/exceptions.py +89 -26
  221. reflex/utils/exec.py +69 -74
  222. reflex/utils/export.py +6 -1
  223. reflex/utils/format.py +8 -40
  224. reflex/utils/imports.py +5 -2
  225. reflex/utils/lazy_loader.py +7 -1
  226. reflex/utils/path_ops.py +74 -14
  227. reflex/utils/prerequisites.py +345 -68
  228. reflex/utils/processes.py +45 -32
  229. reflex/utils/pyi_generator.py +39 -33
  230. reflex/utils/registry.py +4 -4
  231. reflex/utils/serializers.py +1 -1
  232. reflex/utils/telemetry.py +5 -4
  233. reflex/utils/types.py +42 -18
  234. reflex/vars/base.py +695 -330
  235. reflex/vars/datetime.py +6 -7
  236. reflex/vars/dep_tracking.py +344 -0
  237. reflex/vars/function.py +11 -5
  238. reflex/vars/number.py +31 -43
  239. reflex/vars/object.py +74 -64
  240. reflex/vars/sequence.py +79 -67
  241. {reflex-0.6.8a2.dist-info → reflex-0.7.0.dist-info}/METADATA +7 -8
  242. reflex-0.7.0.dist-info/RECORD +401 -0
  243. {reflex-0.6.8a2.dist-info → reflex-0.7.0.dist-info}/WHEEL +1 -1
  244. reflex/experimental/assets.py +0 -37
  245. reflex-0.6.8a2.dist-info/RECORD +0 -397
  246. {reflex-0.6.8a2.dist-info → reflex-0.7.0.dist-info}/LICENSE +0 -0
  247. {reflex-0.6.8a2.dist-info → reflex-0.7.0.dist-info}/entry_points.txt +0 -0
reflex/vars/base.py CHANGED
@@ -5,14 +5,13 @@ from __future__ import annotations
5
5
  import contextlib
6
6
  import dataclasses
7
7
  import datetime
8
- import dis
9
8
  import functools
10
9
  import inspect
11
10
  import json
12
11
  import random
13
12
  import re
14
13
  import string
15
- import sys
14
+ import uuid
16
15
  import warnings
17
16
  from types import CodeType, FunctionType
18
17
  from typing import (
@@ -20,14 +19,17 @@ from typing import (
20
19
  Any,
21
20
  Callable,
22
21
  ClassVar,
22
+ Coroutine,
23
23
  Dict,
24
24
  FrozenSet,
25
25
  Generic,
26
26
  Iterable,
27
27
  List,
28
28
  Literal,
29
+ Mapping,
29
30
  NoReturn,
30
31
  Optional,
32
+ Sequence,
31
33
  Set,
32
34
  Tuple,
33
35
  Type,
@@ -38,6 +40,7 @@ from typing import (
38
40
  overload,
39
41
  )
40
42
 
43
+ from sqlalchemy.orm import DeclarativeBase
41
44
  from typing_extensions import ParamSpec, TypeGuard, deprecated, get_type_hints, override
42
45
 
43
46
  from reflex import constants
@@ -45,10 +48,11 @@ from reflex.base import Base
45
48
  from reflex.constants.compiler import Hooks
46
49
  from reflex.utils import console, exceptions, imports, serializers, types
47
50
  from reflex.utils.exceptions import (
51
+ ComputedVarSignatureError,
52
+ UntypedComputedVarError,
48
53
  VarAttributeError,
49
54
  VarDependencyError,
50
55
  VarTypeError,
51
- VarValueError,
52
56
  )
53
57
  from reflex.utils.format import format_state_name
54
58
  from reflex.utils.imports import (
@@ -64,6 +68,7 @@ from reflex.utils.types import (
64
68
  _isinstance,
65
69
  get_origin,
66
70
  has_args,
71
+ safe_issubclass,
67
72
  unionize,
68
73
  )
69
74
 
@@ -77,6 +82,8 @@ if TYPE_CHECKING:
77
82
 
78
83
  VAR_TYPE = TypeVar("VAR_TYPE", covariant=True)
79
84
  OTHER_VAR_TYPE = TypeVar("OTHER_VAR_TYPE")
85
+ STRING_T = TypeVar("STRING_T", bound=str)
86
+ SEQUENCE_TYPE = TypeVar("SEQUENCE_TYPE", bound=Sequence)
80
87
 
81
88
  warnings.filterwarnings("ignore", message="fields may not start with an underscore")
82
89
 
@@ -127,7 +134,7 @@ class VarData:
127
134
  state: str = "",
128
135
  field_name: str = "",
129
136
  imports: ImportDict | ParsedImportDict | None = None,
130
- hooks: dict[str, VarData | None] | None = None,
137
+ hooks: Mapping[str, VarData | None] | Sequence[str] | str | None = None,
131
138
  deps: list[Var] | None = None,
132
139
  position: Hooks.HookPosition | None = None,
133
140
  ):
@@ -141,10 +148,12 @@ class VarData:
141
148
  deps: Dependencies of the var for useCallback.
142
149
  position: Position of the hook in the component.
143
150
  """
151
+ if isinstance(hooks, str):
152
+ hooks = [hooks]
153
+ if not isinstance(hooks, dict):
154
+ hooks = {hook: None for hook in (hooks or [])}
144
155
  immutable_imports: ImmutableParsedImportDict = tuple(
145
- sorted(
146
- ((k, tuple(sorted(v))) for k, v in parse_imports(imports or {}).items())
147
- )
156
+ (k, tuple(v)) for k, v in parse_imports(imports or {}).items()
148
157
  )
149
158
  object.__setattr__(self, "state", state)
150
159
  object.__setattr__(self, "field_name", field_name)
@@ -153,6 +162,16 @@ class VarData:
153
162
  object.__setattr__(self, "deps", tuple(deps or []))
154
163
  object.__setattr__(self, "position", position or None)
155
164
 
165
+ if hooks and any(hooks.values()):
166
+ merged_var_data = VarData.merge(self, *hooks.values())
167
+ if merged_var_data is not None:
168
+ object.__setattr__(self, "state", merged_var_data.state)
169
+ object.__setattr__(self, "field_name", merged_var_data.field_name)
170
+ object.__setattr__(self, "imports", merged_var_data.imports)
171
+ object.__setattr__(self, "hooks", merged_var_data.hooks)
172
+ object.__setattr__(self, "deps", merged_var_data.deps)
173
+ object.__setattr__(self, "position", merged_var_data.position)
174
+
156
175
  def old_school_imports(self) -> ImportDict:
157
176
  """Return the imports as a mutable dict.
158
177
 
@@ -432,7 +451,7 @@ class Var(Generic[VAR_TYPE]):
432
451
  @dataclasses.dataclass(
433
452
  eq=False,
434
453
  frozen=True,
435
- **{"slots": True} if sys.version_info >= (3, 10) else {},
454
+ slots=True,
436
455
  )
437
456
  class ToVarOperation(ToOperation, cls):
438
457
  """Base class of converting a var to another var type."""
@@ -443,7 +462,12 @@ class Var(Generic[VAR_TYPE]):
443
462
 
444
463
  _default_var_type: ClassVar[GenericType] = default_type
445
464
 
446
- ToVarOperation.__name__ = f'To{cls.__name__.removesuffix("Var")}Operation'
465
+ new_to_var_operation_name = f"To{cls.__name__.removesuffix('Var')}Operation"
466
+ ToVarOperation.__qualname__ = (
467
+ ToVarOperation.__qualname__.removesuffix(ToVarOperation.__name__)
468
+ + new_to_var_operation_name
469
+ )
470
+ ToVarOperation.__name__ = new_to_var_operation_name
447
471
 
448
472
  _var_subclasses.append(VarSubclassEntry(cls, ToVarOperation, python_types))
449
473
 
@@ -492,20 +516,30 @@ class Var(Generic[VAR_TYPE]):
492
516
 
493
517
  @overload
494
518
  def _replace(
495
- self, _var_type: Type[OTHER_VAR_TYPE], merge_var_data=None, **kwargs: Any
519
+ self,
520
+ _var_type: Type[OTHER_VAR_TYPE],
521
+ merge_var_data: VarData | None = None,
522
+ **kwargs: Any,
496
523
  ) -> Var[OTHER_VAR_TYPE]: ...
497
524
 
498
525
  @overload
499
526
  def _replace(
500
- self, _var_type: GenericType | None = None, merge_var_data=None, **kwargs: Any
527
+ self,
528
+ _var_type: GenericType | None = None,
529
+ merge_var_data: VarData | None = None,
530
+ **kwargs: Any,
501
531
  ) -> Self: ...
502
532
 
503
533
  def _replace(
504
- self, _var_type: GenericType | None = None, merge_var_data=None, **kwargs: Any
534
+ self,
535
+ _var_type: GenericType | None = None,
536
+ merge_var_data: VarData | None = None,
537
+ **kwargs: Any,
505
538
  ) -> Self | Var:
506
539
  """Make a copy of this Var with updated fields.
507
540
 
508
541
  Args:
542
+ _var_type: The new type of the Var.
509
543
  merge_var_data: VarData to merge into the existing VarData.
510
544
  **kwargs: Var fields to update.
511
545
 
@@ -539,56 +573,89 @@ class Var(Generic[VAR_TYPE]):
539
573
 
540
574
  return value_with_replaced
541
575
 
576
+ @overload
577
+ @classmethod
578
+ def create( # pyright: ignore[reportOverlappingOverload]
579
+ cls,
580
+ value: bool,
581
+ _var_data: VarData | None = None,
582
+ ) -> BooleanVar: ...
583
+
584
+ @overload
542
585
  @classmethod
543
586
  def create(
544
587
  cls,
545
- value: Any,
546
- _var_is_local: bool | None = None,
547
- _var_is_string: bool | None = None,
588
+ value: int,
548
589
  _var_data: VarData | None = None,
549
- ) -> Var:
590
+ ) -> NumberVar[int]: ...
591
+
592
+ @overload
593
+ @classmethod
594
+ def create(
595
+ cls,
596
+ value: float,
597
+ _var_data: VarData | None = None,
598
+ ) -> NumberVar[float]: ...
599
+
600
+ @overload
601
+ @classmethod
602
+ def create( # pyright: ignore [reportOverlappingOverload]
603
+ cls,
604
+ value: STRING_T,
605
+ _var_data: VarData | None = None,
606
+ ) -> StringVar[STRING_T]: ...
607
+
608
+ @overload
609
+ @classmethod
610
+ def create( # pyright: ignore[reportOverlappingOverload]
611
+ cls,
612
+ value: None,
613
+ _var_data: VarData | None = None,
614
+ ) -> NoneVar: ...
615
+
616
+ @overload
617
+ @classmethod
618
+ def create(
619
+ cls,
620
+ value: MAPPING_TYPE,
621
+ _var_data: VarData | None = None,
622
+ ) -> ObjectVar[MAPPING_TYPE]: ...
623
+
624
+ @overload
625
+ @classmethod
626
+ def create(
627
+ cls,
628
+ value: SEQUENCE_TYPE,
629
+ _var_data: VarData | None = None,
630
+ ) -> ArrayVar[SEQUENCE_TYPE]: ...
631
+
632
+ @overload
633
+ @classmethod
634
+ def create(
635
+ cls,
636
+ value: OTHER_VAR_TYPE,
637
+ _var_data: VarData | None = None,
638
+ ) -> Var[OTHER_VAR_TYPE]: ...
639
+
640
+ @classmethod
641
+ def create(
642
+ cls,
643
+ value: OTHER_VAR_TYPE,
644
+ _var_data: VarData | None = None,
645
+ ) -> Var[OTHER_VAR_TYPE]:
550
646
  """Create a var from a value.
551
647
 
552
648
  Args:
553
649
  value: The value to create the var from.
554
- _var_is_local: Whether the var is local. Deprecated.
555
- _var_is_string: Whether the var is a string literal. Deprecated.
556
650
  _var_data: Additional hooks and imports associated with the Var.
557
651
 
558
652
  Returns:
559
653
  The var.
560
654
  """
561
- if _var_is_local is not None:
562
- console.deprecate(
563
- feature_name="_var_is_local",
564
- reason="The _var_is_local argument is not supported for Var."
565
- "If you want to create a Var from a raw Javascript expression, use the constructor directly",
566
- deprecation_version="0.6.0",
567
- removal_version="0.7.0",
568
- )
569
- if _var_is_string is not None:
570
- console.deprecate(
571
- feature_name="_var_is_string",
572
- reason="The _var_is_string argument is not supported for Var."
573
- "If you want to create a Var from a raw Javascript expression, use the constructor directly",
574
- deprecation_version="0.6.0",
575
- removal_version="0.7.0",
576
- )
577
-
578
655
  # If the value is already a var, do nothing.
579
656
  if isinstance(value, Var):
580
657
  return value
581
658
 
582
- # Try to pull the imports and hooks from contained values.
583
- if not isinstance(value, str):
584
- return LiteralVar.create(value, _var_data=_var_data)
585
-
586
- if _var_is_string is False or _var_is_local is True:
587
- return cls(
588
- _js_expr=value,
589
- _var_data=_var_data,
590
- )
591
-
592
659
  return LiteralVar.create(value, _var_data=_var_data)
593
660
 
594
661
  @classmethod
@@ -643,8 +710,8 @@ class Var(Generic[VAR_TYPE]):
643
710
  @overload
644
711
  def to(
645
712
  self,
646
- output: type[dict],
647
- ) -> ObjectVar[dict]: ...
713
+ output: type[MAPPING_TYPE],
714
+ ) -> ObjectVar[MAPPING_TYPE]: ...
648
715
 
649
716
  @overload
650
717
  def to(
@@ -686,14 +753,16 @@ class Var(Generic[VAR_TYPE]):
686
753
 
687
754
  # If the first argument is a python type, we map it to the corresponding Var type.
688
755
  for var_subclass in _var_subclasses[::-1]:
689
- if fixed_output_type in var_subclass.python_types:
756
+ if fixed_output_type in var_subclass.python_types or safe_issubclass(
757
+ fixed_output_type, var_subclass.python_types
758
+ ):
690
759
  return self.to(var_subclass.var_subclass, output)
691
760
 
692
761
  if fixed_output_type is None:
693
- return get_to_operation(NoneVar).create(self) # type: ignore
762
+ return get_to_operation(NoneVar).create(self) # pyright: ignore [reportReturnType]
694
763
 
695
764
  # Handle fixed_output_type being Base or a dataclass.
696
- if can_use_in_object_var(fixed_output_type):
765
+ if can_use_in_object_var(output):
697
766
  return self.to(ObjectVar, output)
698
767
 
699
768
  if inspect.isclass(output):
@@ -707,7 +776,7 @@ class Var(Generic[VAR_TYPE]):
707
776
  to_operation_return = var_subclass.to_var_subclass.create(
708
777
  value=self, _var_type=new_var_type
709
778
  )
710
- return to_operation_return # type: ignore
779
+ return to_operation_return # pyright: ignore [reportReturnType]
711
780
 
712
781
  # If we can't determine the first argument, we just replace the _var_type.
713
782
  if not issubclass(output, Var) or var_type is None:
@@ -725,6 +794,9 @@ class Var(Generic[VAR_TYPE]):
725
794
 
726
795
  return self
727
796
 
797
+ @overload
798
+ def guess_type(self: Var[NoReturn]) -> Var[Any]: ... # pyright: ignore [reportOverlappingOverload]
799
+
728
800
  @overload
729
801
  def guess_type(self: Var[str]) -> StringVar: ...
730
802
 
@@ -734,6 +806,9 @@ class Var(Generic[VAR_TYPE]):
734
806
  @overload
735
807
  def guess_type(self: Var[int] | Var[float] | Var[int | float]) -> NumberVar: ...
736
808
 
809
+ @overload
810
+ def guess_type(self: Var[BASE_TYPE]) -> ObjectVar[BASE_TYPE]: ...
811
+
737
812
  @overload
738
813
  def guess_type(self) -> Self: ...
739
814
 
@@ -820,7 +895,7 @@ class Var(Generic[VAR_TYPE]):
820
895
  return False
821
896
  if issubclass(type_, list):
822
897
  return []
823
- if issubclass(type_, dict):
898
+ if issubclass(type_, Mapping):
824
899
  return {}
825
900
  if issubclass(type_, tuple):
826
901
  return ()
@@ -882,7 +957,7 @@ class Var(Generic[VAR_TYPE]):
882
957
 
883
958
  return setter
884
959
 
885
- def _var_set_state(self, state: type[BaseState] | str):
960
+ def _var_set_state(self, state: type[BaseState] | str) -> Self:
886
961
  """Set the state of the var.
887
962
 
888
963
  Args:
@@ -897,7 +972,7 @@ class Var(Generic[VAR_TYPE]):
897
972
  else format_state_name(state.get_full_name())
898
973
  )
899
974
 
900
- return StateOperation.create(
975
+ return StateOperation.create( # pyright: ignore [reportReturnType]
901
976
  formatted_state_name,
902
977
  self,
903
978
  _var_data=VarData.merge(
@@ -1026,7 +1101,7 @@ class Var(Generic[VAR_TYPE]):
1026
1101
  f"$/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")]
1027
1102
  }
1028
1103
  ),
1029
- ).to(ObjectVar, Dict[str, str])
1104
+ ).to(ObjectVar, Mapping[str, str])
1030
1105
  return refs[LiteralVar.create(str(self))]
1031
1106
 
1032
1107
  @deprecated("Use `.js_type()` instead.")
@@ -1076,43 +1151,6 @@ class Var(Generic[VAR_TYPE]):
1076
1151
  """
1077
1152
  return self
1078
1153
 
1079
- def __getattr__(self, name: str):
1080
- """Get an attribute of the var.
1081
-
1082
- Args:
1083
- name: The name of the attribute.
1084
-
1085
- Returns:
1086
- The attribute.
1087
-
1088
- Raises:
1089
- VarAttributeError: If the attribute does not exist.
1090
- TypeError: If the var type is Any.
1091
- """
1092
- if name.startswith("_"):
1093
- return self.__getattribute__(name)
1094
-
1095
- if name == "contains":
1096
- raise TypeError(
1097
- f"Var of type {self._var_type} does not support contains check."
1098
- )
1099
- if name == "reverse":
1100
- raise TypeError("Cannot reverse non-list var.")
1101
-
1102
- if self._var_type is Any:
1103
- raise TypeError(
1104
- f"You must provide an annotation for the state var `{self!s}`. Annotation cannot be `{self._var_type}`."
1105
- )
1106
-
1107
- if name in REPLACED_NAMES:
1108
- raise VarAttributeError(
1109
- f"Field {name!r} was renamed to {REPLACED_NAMES[name]!r}"
1110
- )
1111
-
1112
- raise VarAttributeError(
1113
- f"The State var has no attribute '{name}' or may have been annotated wrongly.",
1114
- )
1115
-
1116
1154
  def _decode(self) -> Any:
1117
1155
  """Decode Var as a python value.
1118
1156
 
@@ -1123,7 +1161,7 @@ class Var(Generic[VAR_TYPE]):
1123
1161
  The decoded value or the Var name.
1124
1162
  """
1125
1163
  if isinstance(self, LiteralVar):
1126
- return self._var_value # type: ignore
1164
+ return self._var_value
1127
1165
  try:
1128
1166
  return json.loads(str(self))
1129
1167
  except ValueError:
@@ -1174,36 +1212,76 @@ class Var(Generic[VAR_TYPE]):
1174
1212
 
1175
1213
  return ArrayVar.range(first_endpoint, second_endpoint, step)
1176
1214
 
1177
- def __bool__(self) -> bool:
1178
- """Raise exception if using Var in a boolean context.
1215
+ if not TYPE_CHECKING:
1179
1216
 
1180
- Raises:
1181
- VarTypeError: when attempting to bool-ify the Var.
1182
- """
1183
- raise VarTypeError(
1184
- f"Cannot convert Var {str(self)!r} to bool for use with `if`, `and`, `or`, and `not`. "
1185
- "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)."
1186
- )
1217
+ def __getattr__(self, name: str):
1218
+ """Get an attribute of the var.
1187
1219
 
1188
- def __iter__(self) -> Any:
1189
- """Raise exception if using Var in an iterable context.
1220
+ Args:
1221
+ name: The name of the attribute.
1190
1222
 
1191
- Raises:
1192
- VarTypeError: when attempting to iterate over the Var.
1193
- """
1194
- raise VarTypeError(
1195
- f"Cannot iterate over Var {str(self)!r}. Instead use `rx.foreach`."
1196
- )
1223
+ Raises:
1224
+ VarAttributeError: If the attribute does not exist.
1225
+ UntypedVarError: If the var type is Any.
1226
+ TypeError: If the var type is Any.
1197
1227
 
1198
- def __contains__(self, _: Any) -> Var:
1199
- """Override the 'in' operator to alert the user that it is not supported.
1228
+ # noqa: DAR101 self
1229
+ """
1230
+ if name.startswith("_"):
1231
+ raise VarAttributeError(f"Attribute {name} not found.")
1200
1232
 
1201
- Raises:
1202
- VarTypeError: the operation is not supported
1203
- """
1204
- raise VarTypeError(
1205
- "'in' operator not supported for Var types, use Var.contains() instead."
1206
- )
1233
+ if name == "contains":
1234
+ raise TypeError(
1235
+ f"Var of type {self._var_type} does not support contains check."
1236
+ )
1237
+ if name == "reverse":
1238
+ raise TypeError("Cannot reverse non-list var.")
1239
+
1240
+ if self._var_type is Any:
1241
+ raise exceptions.UntypedVarError(
1242
+ f"You must provide an annotation for the state var `{self!s}`. Annotation cannot be `{self._var_type}`."
1243
+ )
1244
+
1245
+ raise VarAttributeError(
1246
+ f"The State var has no attribute '{name}' or may have been annotated wrongly.",
1247
+ )
1248
+
1249
+ def __bool__(self) -> bool:
1250
+ """Raise exception if using Var in a boolean context.
1251
+
1252
+ Raises:
1253
+ VarTypeError: when attempting to bool-ify the Var.
1254
+
1255
+ # noqa: DAR101 self
1256
+ """
1257
+ raise VarTypeError(
1258
+ f"Cannot convert Var {str(self)!r} to bool for use with `if`, `and`, `or`, and `not`. "
1259
+ "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)."
1260
+ )
1261
+
1262
+ def __iter__(self) -> Any:
1263
+ """Raise exception if using Var in an iterable context.
1264
+
1265
+ Raises:
1266
+ VarTypeError: when attempting to iterate over the Var.
1267
+
1268
+ # noqa: DAR101 self
1269
+ """
1270
+ raise VarTypeError(
1271
+ f"Cannot iterate over Var {str(self)!r}. Instead use `rx.foreach`."
1272
+ )
1273
+
1274
+ def __contains__(self, _: Any) -> Var:
1275
+ """Override the 'in' operator to alert the user that it is not supported.
1276
+
1277
+ Raises:
1278
+ VarTypeError: the operation is not supported
1279
+
1280
+ # noqa: DAR101 self
1281
+ """
1282
+ raise VarTypeError(
1283
+ "'in' operator not supported for Var types, use Var.contains() instead."
1284
+ )
1207
1285
 
1208
1286
 
1209
1287
  OUTPUT = TypeVar("OUTPUT", bound=Var)
@@ -1250,7 +1328,7 @@ class ToOperation:
1250
1328
  """
1251
1329
  return VarData.merge(
1252
1330
  self._original._get_all_var_data(),
1253
- self._var_data, # type: ignore
1331
+ self._var_data,
1254
1332
  )
1255
1333
 
1256
1334
  @classmethod
@@ -1271,10 +1349,10 @@ class ToOperation:
1271
1349
  The ToOperation.
1272
1350
  """
1273
1351
  return cls(
1274
- _js_expr="", # type: ignore
1275
- _var_data=_var_data, # type: ignore
1276
- _var_type=_var_type or cls._default_var_type, # type: ignore
1277
- _original=value, # type: ignore
1352
+ _js_expr="", # pyright: ignore [reportCallIssue]
1353
+ _var_data=_var_data, # pyright: ignore [reportCallIssue]
1354
+ _var_type=_var_type or cls._default_var_type, # pyright: ignore [reportCallIssue, reportAttributeAccessIssue]
1355
+ _original=value, # pyright: ignore [reportCallIssue]
1278
1356
  )
1279
1357
 
1280
1358
 
@@ -1336,7 +1414,7 @@ class LiteralVar(Var):
1336
1414
  _var_literal_subclasses.append((cls, var_subclass))
1337
1415
 
1338
1416
  @classmethod
1339
- def create(
1417
+ def create( # pyright: ignore [reportArgumentType]
1340
1418
  cls,
1341
1419
  value: Any,
1342
1420
  _var_data: VarData | None = None,
@@ -1354,7 +1432,7 @@ class LiteralVar(Var):
1354
1432
  TypeError: If the value is not a supported type for LiteralVar.
1355
1433
  """
1356
1434
  from .object import LiteralObjectVar
1357
- from .sequence import LiteralStringVar
1435
+ from .sequence import ArrayVar, LiteralStringVar
1358
1436
 
1359
1437
  if isinstance(value, Var):
1360
1438
  if _var_data is None:
@@ -1373,7 +1451,7 @@ class LiteralVar(Var):
1373
1451
 
1374
1452
  serialized_value = serializers.serialize(value)
1375
1453
  if serialized_value is not None:
1376
- if isinstance(serialized_value, dict):
1454
+ if isinstance(serialized_value, Mapping):
1377
1455
  return LiteralObjectVar.create(
1378
1456
  serialized_value,
1379
1457
  _var_type=type(value),
@@ -1410,6 +1488,9 @@ class LiteralVar(Var):
1410
1488
  _var_data=_var_data,
1411
1489
  )
1412
1490
 
1491
+ if isinstance(value, range):
1492
+ return ArrayVar.range(value.start, value.stop, value.step)
1493
+
1413
1494
  raise TypeError(
1414
1495
  f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}."
1415
1496
  )
@@ -1417,6 +1498,12 @@ class LiteralVar(Var):
1417
1498
  def __post_init__(self):
1418
1499
  """Post-initialize the var."""
1419
1500
 
1501
+ @property
1502
+ def _var_value(self) -> Any:
1503
+ raise NotImplementedError(
1504
+ "LiteralVar subclasses must implement the _var_value property."
1505
+ )
1506
+
1420
1507
  def json(self) -> str:
1421
1508
  """Serialize the var to a JSON string.
1422
1509
 
@@ -1463,7 +1550,7 @@ T = TypeVar("T")
1463
1550
 
1464
1551
  # NoReturn is used to match CustomVarOperationReturn with no type hint.
1465
1552
  @overload
1466
- def var_operation(
1553
+ def var_operation( # pyright: ignore [reportOverlappingOverload]
1467
1554
  func: Callable[P, CustomVarOperationReturn[NoReturn]],
1468
1555
  ) -> Callable[P, Var]: ...
1469
1556
 
@@ -1489,7 +1576,7 @@ def var_operation(
1489
1576
  ) -> Callable[P, StringVar]: ...
1490
1577
 
1491
1578
 
1492
- LIST_T = TypeVar("LIST_T", bound=Union[List[Any], Tuple, Set])
1579
+ LIST_T = TypeVar("LIST_T", bound=Sequence)
1493
1580
 
1494
1581
 
1495
1582
  @overload
@@ -1498,7 +1585,7 @@ def var_operation(
1498
1585
  ) -> Callable[P, ArrayVar[LIST_T]]: ...
1499
1586
 
1500
1587
 
1501
- OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Dict)
1588
+ OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Mapping)
1502
1589
 
1503
1590
 
1504
1591
  @overload
@@ -1513,7 +1600,7 @@ def var_operation(
1513
1600
  ) -> Callable[P, Var[T]]: ...
1514
1601
 
1515
1602
 
1516
- def var_operation(
1603
+ def var_operation( # pyright: ignore [reportInconsistentOverload]
1517
1604
  func: Callable[P, CustomVarOperationReturn[T]],
1518
1605
  ) -> Callable[P, Var[T]]:
1519
1606
  """Decorator for creating a var operation.
@@ -1547,7 +1634,7 @@ def var_operation(
1547
1634
  return CustomVarOperation.create(
1548
1635
  name=func.__name__,
1549
1636
  args=tuple(list(args_vars.items()) + list(kwargs_vars.items())),
1550
- return_var=func(*args_vars.values(), **kwargs_vars), # type: ignore
1637
+ return_var=func(*args_vars.values(), **kwargs_vars), # pyright: ignore [reportCallIssue, reportReturnType]
1551
1638
  ).guess_type()
1552
1639
 
1553
1640
  return wrapper
@@ -1573,25 +1660,100 @@ def figure_out_type(value: Any) -> types.GenericType:
1573
1660
  return Set[unionize(*(figure_out_type(v) for v in value))]
1574
1661
  if isinstance(value, tuple):
1575
1662
  return Tuple[unionize(*(figure_out_type(v) for v in value)), ...]
1576
- if isinstance(value, dict):
1577
- return Dict[
1663
+ if isinstance(value, Mapping):
1664
+ return Mapping[
1578
1665
  unionize(*(figure_out_type(k) for k in value)),
1579
1666
  unionize(*(figure_out_type(v) for v in value.values())),
1580
1667
  ]
1581
1668
  return type(value)
1582
1669
 
1583
1670
 
1584
- class cached_property_no_lock(functools.cached_property):
1585
- """A special version of functools.cached_property that does not use a lock."""
1671
+ GLOBAL_CACHE = {}
1672
+
1673
+
1674
+ class cached_property: # noqa: N801
1675
+ """A cached property that caches the result of the function."""
1586
1676
 
1587
- def __init__(self, func):
1588
- """Initialize the cached_property_no_lock.
1677
+ def __init__(self, func: Callable):
1678
+ """Initialize the cached_property.
1589
1679
 
1590
1680
  Args:
1591
1681
  func: The function to cache.
1592
1682
  """
1593
- super().__init__(func)
1594
- self.lock = contextlib.nullcontext()
1683
+ self._func = func
1684
+ self._attrname = None
1685
+
1686
+ def __set_name__(self, owner: Any, name: str):
1687
+ """Set the name of the cached property.
1688
+
1689
+ Args:
1690
+ owner: The owner of the cached property.
1691
+ name: The name of the cached property.
1692
+
1693
+ Raises:
1694
+ TypeError: If the cached property is assigned to two different names.
1695
+ """
1696
+ if self._attrname is None:
1697
+ self._attrname = name
1698
+
1699
+ original_del = getattr(owner, "__del__", None)
1700
+
1701
+ def delete_property(this: Any):
1702
+ """Delete the cached property.
1703
+
1704
+ Args:
1705
+ this: The object to delete the cached property from.
1706
+ """
1707
+ cached_field_name = "_reflex_cache_" + name
1708
+ try:
1709
+ unique_id = object.__getattribute__(this, cached_field_name)
1710
+ except AttributeError:
1711
+ if original_del is not None:
1712
+ original_del(this)
1713
+ return
1714
+ if unique_id in GLOBAL_CACHE:
1715
+ del GLOBAL_CACHE[unique_id]
1716
+
1717
+ if original_del is not None:
1718
+ original_del(this)
1719
+
1720
+ owner.__del__ = delete_property
1721
+
1722
+ elif name != self._attrname:
1723
+ raise TypeError(
1724
+ "Cannot assign the same cached_property to two different names "
1725
+ f"({self._attrname!r} and {name!r})."
1726
+ )
1727
+
1728
+ def __get__(self, instance: Any, owner: Type | None = None):
1729
+ """Get the cached property.
1730
+
1731
+ Args:
1732
+ instance: The instance to get the cached property from.
1733
+ owner: The owner of the cached property.
1734
+
1735
+ Returns:
1736
+ The cached property.
1737
+
1738
+ Raises:
1739
+ TypeError: If the class does not have __set_name__.
1740
+ """
1741
+ if self._attrname is None:
1742
+ raise TypeError(
1743
+ "Cannot use cached_property on a class without __set_name__."
1744
+ )
1745
+ cached_field_name = "_reflex_cache_" + self._attrname
1746
+ try:
1747
+ unique_id = object.__getattribute__(instance, cached_field_name)
1748
+ except AttributeError:
1749
+ unique_id = uuid.uuid4().int
1750
+ object.__setattr__(instance, cached_field_name, unique_id)
1751
+ if unique_id not in GLOBAL_CACHE:
1752
+ GLOBAL_CACHE[unique_id] = self._func(instance)
1753
+ return GLOBAL_CACHE[unique_id]
1754
+
1755
+
1756
+ cached_property_no_lock = cached_property
1595
1757
 
1596
1758
 
1597
1759
  class CachedVarOperation:
@@ -1617,7 +1779,7 @@ class CachedVarOperation:
1617
1779
 
1618
1780
  next_class = parent_classes[parent_classes.index(CachedVarOperation) + 1]
1619
1781
 
1620
- return next_class.__getattr__(self, name) # type: ignore
1782
+ return next_class.__getattr__(self, name)
1621
1783
 
1622
1784
  def _get_all_var_data(self) -> VarData | None:
1623
1785
  """Get all VarData associated with the Var.
@@ -1639,7 +1801,7 @@ class CachedVarOperation:
1639
1801
  value._get_all_var_data() if isinstance(value, Var) else None
1640
1802
  for value in (
1641
1803
  getattr(self, field.name)
1642
- for field in dataclasses.fields(self) # type: ignore
1804
+ for field in dataclasses.fields(self) # pyright: ignore [reportArgumentType]
1643
1805
  )
1644
1806
  ),
1645
1807
  self._var_data,
@@ -1656,7 +1818,7 @@ class CachedVarOperation:
1656
1818
  type(self).__name__,
1657
1819
  *[
1658
1820
  getattr(self, field.name)
1659
- for field in dataclasses.fields(self) # type: ignore
1821
+ for field in dataclasses.fields(self) # pyright: ignore [reportArgumentType]
1660
1822
  if field.name not in ["_js_expr", "_var_data", "_var_type"]
1661
1823
  ],
1662
1824
  )
@@ -1673,7 +1835,7 @@ def and_operation(a: Var | Any, b: Var | Any) -> Var:
1673
1835
  Returns:
1674
1836
  The result of the logical AND operation.
1675
1837
  """
1676
- return _and_operation(a, b) # type: ignore
1838
+ return _and_operation(a, b)
1677
1839
 
1678
1840
 
1679
1841
  @var_operation
@@ -1703,7 +1865,7 @@ def or_operation(a: Var | Any, b: Var | Any) -> Var:
1703
1865
  Returns:
1704
1866
  The result of the logical OR operation.
1705
1867
  """
1706
- return _or_operation(a, b) # type: ignore
1868
+ return _or_operation(a, b)
1707
1869
 
1708
1870
 
1709
1871
  @var_operation
@@ -1726,7 +1888,7 @@ def _or_operation(a: Var, b: Var):
1726
1888
  @dataclasses.dataclass(
1727
1889
  eq=False,
1728
1890
  frozen=True,
1729
- **{"slots": True} if sys.version_info >= (3, 10) else {},
1891
+ slots=True,
1730
1892
  )
1731
1893
  class CallableVar(Var):
1732
1894
  """Decorate a Var-returning function to act as both a Var and a function.
@@ -1757,7 +1919,7 @@ class CallableVar(Var):
1757
1919
  object.__setattr__(self, "fn", fn)
1758
1920
  object.__setattr__(self, "original_var", original_var)
1759
1921
 
1760
- def __call__(self, *args, **kwargs) -> Var:
1922
+ def __call__(self, *args: Any, **kwargs: Any) -> Var:
1761
1923
  """Call the decorated function.
1762
1924
 
1763
1925
  Args:
@@ -1807,7 +1969,7 @@ def is_computed_var(obj: Any) -> TypeGuard[ComputedVar]:
1807
1969
  @dataclasses.dataclass(
1808
1970
  eq=False,
1809
1971
  frozen=True,
1810
- **{"slots": True} if sys.version_info >= (3, 10) else {},
1972
+ slots=True,
1811
1973
  )
1812
1974
  class ComputedVar(Var[RETURN_TYPE]):
1813
1975
  """A field with computed getters."""
@@ -1822,7 +1984,7 @@ class ComputedVar(Var[RETURN_TYPE]):
1822
1984
  _initial_value: RETURN_TYPE | types.Unset = dataclasses.field(default=types.Unset())
1823
1985
 
1824
1986
  # Explicit var dependencies to track
1825
- _static_deps: set[str] = dataclasses.field(default_factory=set)
1987
+ _static_deps: dict[str | None, set[str]] = dataclasses.field(default_factory=dict)
1826
1988
 
1827
1989
  # Whether var dependencies should be auto-determined
1828
1990
  _auto_deps: bool = dataclasses.field(default=True)
@@ -1832,13 +1994,13 @@ class ComputedVar(Var[RETURN_TYPE]):
1832
1994
 
1833
1995
  _fget: Callable[[BaseState], RETURN_TYPE] = dataclasses.field(
1834
1996
  default_factory=lambda: lambda _: None
1835
- ) # type: ignore
1997
+ ) # pyright: ignore [reportAssignmentType]
1836
1998
 
1837
1999
  def __init__(
1838
2000
  self,
1839
2001
  fget: Callable[[BASE_STATE], RETURN_TYPE],
1840
2002
  initial_value: RETURN_TYPE | types.Unset = types.Unset(),
1841
- cache: bool = False,
2003
+ cache: bool = True,
1842
2004
  deps: Optional[List[Union[str, Var]]] = None,
1843
2005
  auto_deps: bool = True,
1844
2006
  interval: Optional[Union[int, datetime.timedelta]] = None,
@@ -1859,19 +2021,14 @@ class ComputedVar(Var[RETURN_TYPE]):
1859
2021
 
1860
2022
  Raises:
1861
2023
  TypeError: If the computed var dependencies are not Var instances or var names.
2024
+ UntypedComputedVarError: If the computed var is untyped.
1862
2025
  """
1863
2026
  hint = kwargs.pop("return_type", None) or get_type_hints(fget).get(
1864
2027
  "return", Any
1865
2028
  )
1866
2029
 
1867
2030
  if hint is Any:
1868
- console.deprecate(
1869
- "untyped-computed-var",
1870
- "ComputedVar should have a return type annotation.",
1871
- "0.6.5",
1872
- "0.7.0",
1873
- )
1874
-
2031
+ raise UntypedComputedVarError(var_name=fget.__name__)
1875
2032
  kwargs.setdefault("_js_expr", fget.__name__)
1876
2033
  kwargs.setdefault("_var_type", hint)
1877
2034
 
@@ -1897,28 +2054,78 @@ class ComputedVar(Var[RETURN_TYPE]):
1897
2054
 
1898
2055
  object.__setattr__(self, "_update_interval", interval)
1899
2056
 
1900
- if deps is None:
1901
- deps = []
1902
- else:
1903
- for dep in deps:
1904
- if isinstance(dep, Var):
1905
- continue
1906
- if isinstance(dep, str) and dep != "":
1907
- continue
1908
- raise TypeError(
1909
- "ComputedVar dependencies must be Var instances or var names (non-empty strings)."
1910
- )
1911
2057
  object.__setattr__(
1912
2058
  self,
1913
2059
  "_static_deps",
1914
- {dep._js_expr if isinstance(dep, Var) else dep for dep in deps},
2060
+ self._calculate_static_deps(deps),
1915
2061
  )
1916
2062
  object.__setattr__(self, "_auto_deps", auto_deps)
1917
2063
 
1918
2064
  object.__setattr__(self, "_fget", fget)
1919
2065
 
2066
+ def _calculate_static_deps(
2067
+ self,
2068
+ deps: Union[List[Union[str, Var]], dict[str | None, set[str]]] | None = None,
2069
+ ) -> dict[str | None, set[str]]:
2070
+ """Calculate the static dependencies of the computed var from user input or existing dependencies.
2071
+
2072
+ Args:
2073
+ deps: The user input dependencies or existing dependencies.
2074
+
2075
+ Returns:
2076
+ The static dependencies.
2077
+ """
2078
+ if isinstance(deps, dict):
2079
+ # Assume a dict is coming from _replace, so no special processing.
2080
+ return deps
2081
+ _static_deps = {}
2082
+ if deps is not None:
2083
+ for dep in deps:
2084
+ _static_deps = self._add_static_dep(dep, _static_deps)
2085
+ return _static_deps
2086
+
2087
+ def _add_static_dep(
2088
+ self, dep: Union[str, Var], deps: dict[str | None, set[str]] | None = None
2089
+ ) -> dict[str | None, set[str]]:
2090
+ """Add a static dependency to the computed var or existing dependency set.
2091
+
2092
+ Args:
2093
+ dep: The dependency to add.
2094
+ deps: The existing dependency set.
2095
+
2096
+ Returns:
2097
+ The updated dependency set.
2098
+
2099
+ Raises:
2100
+ TypeError: If the computed var dependencies are not Var instances or var names.
2101
+ """
2102
+ if deps is None:
2103
+ deps = self._static_deps
2104
+ if isinstance(dep, Var):
2105
+ state_name = (
2106
+ all_var_data.state
2107
+ if (all_var_data := dep._get_all_var_data()) and all_var_data.state
2108
+ else None
2109
+ )
2110
+ if all_var_data is not None:
2111
+ var_name = all_var_data.field_name
2112
+ else:
2113
+ var_name = dep._js_expr
2114
+ deps.setdefault(state_name, set()).add(var_name)
2115
+ elif isinstance(dep, str) and dep != "":
2116
+ deps.setdefault(None, set()).add(dep)
2117
+ else:
2118
+ raise TypeError(
2119
+ "ComputedVar dependencies must be Var instances or var names (non-empty strings)."
2120
+ )
2121
+ return deps
2122
+
1920
2123
  @override
1921
- def _replace(self, merge_var_data=None, **kwargs: Any) -> Self:
2124
+ def _replace(
2125
+ self,
2126
+ merge_var_data: VarData | None = None,
2127
+ **kwargs: Any,
2128
+ ) -> Self:
1922
2129
  """Replace the attributes of the ComputedVar.
1923
2130
 
1924
2131
  Args:
@@ -1931,6 +2138,8 @@ class ComputedVar(Var[RETURN_TYPE]):
1931
2138
  Raises:
1932
2139
  TypeError: If kwargs contains keys that are not allowed.
1933
2140
  """
2141
+ if "deps" in kwargs:
2142
+ kwargs["deps"] = self._calculate_static_deps(kwargs["deps"])
1934
2143
  field_values = {
1935
2144
  "fget": kwargs.pop("fget", self._fget),
1936
2145
  "initial_value": kwargs.pop("initial_value", self._initial_value),
@@ -1944,6 +2153,7 @@ class ComputedVar(Var[RETURN_TYPE]):
1944
2153
  "_var_data": kwargs.pop(
1945
2154
  "_var_data", VarData.merge(self._var_data, merge_var_data)
1946
2155
  ),
2156
+ "return_type": kwargs.pop("return_type", self._var_type),
1947
2157
  }
1948
2158
 
1949
2159
  if kwargs:
@@ -1986,6 +2196,13 @@ class ComputedVar(Var[RETURN_TYPE]):
1986
2196
  return True
1987
2197
  return datetime.datetime.now() - last_updated > self._update_interval
1988
2198
 
2199
+ @overload
2200
+ def __get__(
2201
+ self: ComputedVar[bool],
2202
+ instance: None,
2203
+ owner: Type,
2204
+ ) -> BooleanVar: ...
2205
+
1989
2206
  @overload
1990
2207
  def __get__(
1991
2208
  self: ComputedVar[int] | ComputedVar[float],
@@ -2002,10 +2219,10 @@ class ComputedVar(Var[RETURN_TYPE]):
2002
2219
 
2003
2220
  @overload
2004
2221
  def __get__(
2005
- self: ComputedVar[dict[DICT_KEY, DICT_VAL]],
2222
+ self: ComputedVar[MAPPING_TYPE],
2006
2223
  instance: None,
2007
2224
  owner: Type,
2008
- ) -> ObjectVar[dict[DICT_KEY, DICT_VAL]]: ...
2225
+ ) -> ObjectVar[MAPPING_TYPE]: ...
2009
2226
 
2010
2227
  @overload
2011
2228
  def __get__(
@@ -2016,17 +2233,31 @@ class ComputedVar(Var[RETURN_TYPE]):
2016
2233
 
2017
2234
  @overload
2018
2235
  def __get__(
2019
- self: ComputedVar[set[LIST_INSIDE]],
2236
+ self: ComputedVar[tuple[LIST_INSIDE, ...]],
2020
2237
  instance: None,
2021
2238
  owner: Type,
2022
- ) -> ArrayVar[set[LIST_INSIDE]]: ...
2239
+ ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ...
2023
2240
 
2024
2241
  @overload
2025
2242
  def __get__(
2026
- self: ComputedVar[tuple[LIST_INSIDE, ...]],
2243
+ self: ComputedVar[BASE_TYPE],
2027
2244
  instance: None,
2028
2245
  owner: Type,
2029
- ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ...
2246
+ ) -> ObjectVar[BASE_TYPE]: ...
2247
+
2248
+ @overload
2249
+ def __get__(
2250
+ self: ComputedVar[SQLA_TYPE],
2251
+ instance: None,
2252
+ owner: Type,
2253
+ ) -> ObjectVar[SQLA_TYPE]: ...
2254
+
2255
+ if TYPE_CHECKING:
2256
+
2257
+ @overload
2258
+ def __get__(
2259
+ self: ComputedVar[DATACLASS_TYPE], instance: None, owner: Any
2260
+ ) -> ObjectVar[DATACLASS_TYPE]: ...
2030
2261
 
2031
2262
  @overload
2032
2263
  def __get__(self, instance: None, owner: Type) -> ComputedVar[RETURN_TYPE]: ...
@@ -2034,7 +2265,7 @@ class ComputedVar(Var[RETURN_TYPE]):
2034
2265
  @overload
2035
2266
  def __get__(self, instance: BaseState, owner: Type) -> RETURN_TYPE: ...
2036
2267
 
2037
- def __get__(self, instance: BaseState | None, owner):
2268
+ def __get__(self, instance: BaseState | None, owner: Type):
2038
2269
  """Get the ComputedVar value.
2039
2270
 
2040
2271
  If the value is already cached on the instance, return the cached value.
@@ -2077,130 +2308,69 @@ class ComputedVar(Var[RETURN_TYPE]):
2077
2308
  setattr(instance, self._last_updated_attr, datetime.datetime.now())
2078
2309
  value = getattr(instance, self._cache_attr)
2079
2310
 
2080
- if not _isinstance(value, self._var_type):
2081
- console.deprecate(
2082
- "mismatched-computed-var-return",
2083
- f"Computed var {type(instance).__name__}.{self._js_expr} returned value of type {type(value)}, "
2084
- f"expected {self._var_type}. This might cause unexpected behavior.",
2085
- "0.6.5",
2086
- "0.7.0",
2087
- )
2311
+ self._check_deprecated_return_type(instance, value)
2088
2312
 
2089
2313
  return value
2090
2314
 
2315
+ def _check_deprecated_return_type(self, instance: BaseState, value: Any) -> None:
2316
+ if not _isinstance(value, self._var_type):
2317
+ console.error(
2318
+ f"Computed var '{type(instance).__name__}.{self._js_expr}' must return"
2319
+ f" type '{self._var_type}', got '{type(value)}'."
2320
+ )
2321
+
2091
2322
  def _deps(
2092
2323
  self,
2093
- objclass: Type,
2324
+ objclass: Type[BaseState],
2094
2325
  obj: FunctionType | CodeType | None = None,
2095
- self_name: Optional[str] = None,
2096
- ) -> set[str]:
2326
+ ) -> dict[str, set[str]]:
2097
2327
  """Determine var dependencies of this ComputedVar.
2098
2328
 
2099
- Save references to attributes accessed on "self". Recursively called
2100
- when the function makes a method call on "self" or define comprehensions
2101
- or nested functions that may reference "self".
2329
+ Save references to attributes accessed on "self" or other fetched states.
2330
+
2331
+ Recursively called when the function makes a method call on "self" or
2332
+ define comprehensions or nested functions that may reference "self".
2102
2333
 
2103
2334
  Args:
2104
2335
  objclass: the class obj this ComputedVar is attached to.
2105
2336
  obj: the object to disassemble (defaults to the fget function).
2106
- self_name: if specified, look for this name in LOAD_FAST and LOAD_DEREF instructions.
2107
2337
 
2108
2338
  Returns:
2109
- A set of variable names accessed by the given obj.
2110
-
2111
- Raises:
2112
- VarValueError: if the function references the get_state, parent_state, or substates attributes
2113
- (cannot track deps in a related state, only implicitly via parent state).
2339
+ A dictionary mapping state names to the set of variable names
2340
+ accessed by the given obj.
2114
2341
  """
2342
+ from .dep_tracking import DependencyTracker
2343
+
2344
+ d = {}
2345
+ if self._static_deps:
2346
+ d.update(self._static_deps)
2347
+ # None is a placeholder for the current state class.
2348
+ if None in d:
2349
+ d[objclass.get_full_name()] = d.pop(None)
2350
+
2115
2351
  if not self._auto_deps:
2116
- return self._static_deps
2117
- d = self._static_deps.copy()
2352
+ return d
2353
+
2118
2354
  if obj is None:
2119
2355
  fget = self._fget
2120
2356
  if fget is not None:
2121
2357
  obj = cast(FunctionType, fget)
2122
2358
  else:
2123
- return set()
2124
- with contextlib.suppress(AttributeError):
2125
- # unbox functools.partial
2126
- obj = cast(FunctionType, obj.func) # type: ignore
2127
- with contextlib.suppress(AttributeError):
2128
- # unbox EventHandler
2129
- obj = cast(FunctionType, obj.fn) # type: ignore
2359
+ return d
2130
2360
 
2131
- if self_name is None and isinstance(obj, FunctionType):
2132
- try:
2133
- # the first argument to the function is the name of "self" arg
2134
- self_name = obj.__code__.co_varnames[0]
2135
- except (AttributeError, IndexError):
2136
- self_name = None
2137
- if self_name is None:
2138
- # cannot reference attributes on self if method takes no args
2139
- return set()
2140
-
2141
- invalid_names = ["get_state", "parent_state", "substates", "get_substate"]
2142
- self_is_top_of_stack = False
2143
- for instruction in dis.get_instructions(obj):
2144
- if (
2145
- instruction.opname in ("LOAD_FAST", "LOAD_DEREF")
2146
- and instruction.argval == self_name
2147
- ):
2148
- # bytecode loaded the class instance to the top of stack, next load instruction
2149
- # is referencing an attribute on self
2150
- self_is_top_of_stack = True
2151
- continue
2152
- if self_is_top_of_stack and instruction.opname in (
2153
- "LOAD_ATTR",
2154
- "LOAD_METHOD",
2155
- ):
2156
- try:
2157
- ref_obj = getattr(objclass, instruction.argval)
2158
- except Exception:
2159
- ref_obj = None
2160
- if instruction.argval in invalid_names:
2161
- raise VarValueError(
2162
- f"Cached var {self!s} cannot access arbitrary state via `{instruction.argval}`."
2163
- )
2164
- if callable(ref_obj):
2165
- # recurse into callable attributes
2166
- d.update(
2167
- self._deps(
2168
- objclass=objclass,
2169
- obj=ref_obj,
2170
- )
2171
- )
2172
- # recurse into property fget functions
2173
- elif isinstance(ref_obj, property) and not isinstance(
2174
- ref_obj, ComputedVar
2175
- ):
2176
- d.update(
2177
- self._deps(
2178
- objclass=objclass,
2179
- obj=ref_obj.fget, # type: ignore
2180
- )
2181
- )
2182
- elif (
2183
- instruction.argval in objclass.backend_vars
2184
- or instruction.argval in objclass.vars
2185
- ):
2186
- # var access
2187
- d.add(instruction.argval)
2188
- elif instruction.opname == "LOAD_CONST" and isinstance(
2189
- instruction.argval, CodeType
2190
- ):
2191
- # recurse into nested functions / comprehensions, which can reference
2192
- # instance attributes from the outer scope
2193
- d.update(
2194
- self._deps(
2195
- objclass=objclass,
2196
- obj=instruction.argval,
2197
- self_name=self_name,
2198
- )
2199
- )
2200
- self_is_top_of_stack = False
2201
- return d
2361
+ try:
2362
+ return DependencyTracker(
2363
+ func=obj, state_cls=objclass, dependencies=d
2364
+ ).dependencies
2365
+ except Exception as e:
2366
+ console.warn(
2367
+ "Failed to automatically determine dependencies for computed var "
2368
+ f"{objclass.__name__}.{self._js_expr}: {e}. "
2369
+ "Provide static_deps and set auto_deps=False to suppress this warning."
2370
+ )
2371
+ return d
2202
2372
 
2203
- def mark_dirty(self, instance) -> None:
2373
+ def mark_dirty(self, instance: BaseState) -> None:
2204
2374
  """Mark this ComputedVar as dirty.
2205
2375
 
2206
2376
  Args:
@@ -2209,6 +2379,37 @@ class ComputedVar(Var[RETURN_TYPE]):
2209
2379
  with contextlib.suppress(AttributeError):
2210
2380
  delattr(instance, self._cache_attr)
2211
2381
 
2382
+ def add_dependency(self, objclass: Type[BaseState], dep: Var):
2383
+ """Explicitly add a dependency to the ComputedVar.
2384
+
2385
+ After adding the dependency, when the `dep` changes, this computed var
2386
+ will be marked dirty.
2387
+
2388
+ Args:
2389
+ objclass: The class obj this ComputedVar is attached to.
2390
+ dep: The dependency to add.
2391
+
2392
+ Raises:
2393
+ VarDependencyError: If the dependency is not a Var instance with a
2394
+ state and field name
2395
+ """
2396
+ if all_var_data := dep._get_all_var_data():
2397
+ state_name = all_var_data.state
2398
+ if state_name:
2399
+ var_name = all_var_data.field_name
2400
+ if var_name:
2401
+ self._static_deps.setdefault(state_name, set()).add(var_name)
2402
+ objclass.get_root_state().get_class_substate(
2403
+ state_name
2404
+ )._var_dependencies.setdefault(var_name, set()).add(
2405
+ (objclass.get_full_name(), self._js_expr)
2406
+ )
2407
+ return
2408
+ raise VarDependencyError(
2409
+ "ComputedVar dependencies must be Var instances with a state and "
2410
+ f"field name, got {dep!r}."
2411
+ )
2412
+
2212
2413
  def _determine_var_type(self) -> Type:
2213
2414
  """Get the type of the var.
2214
2415
 
@@ -2218,7 +2419,7 @@ class ComputedVar(Var[RETURN_TYPE]):
2218
2419
  hints = get_type_hints(self._fget)
2219
2420
  if "return" in hints:
2220
2421
  return hints["return"]
2221
- return Any
2422
+ return Any # pyright: ignore [reportReturnType]
2222
2423
 
2223
2424
  @property
2224
2425
  def __class__(self) -> Type:
@@ -2245,6 +2446,147 @@ class DynamicRouteVar(ComputedVar[Union[str, List[str]]]):
2245
2446
  pass
2246
2447
 
2247
2448
 
2449
+ async def _default_async_computed_var(_self: BaseState) -> Any:
2450
+ return None
2451
+
2452
+
2453
+ @dataclasses.dataclass(
2454
+ eq=False,
2455
+ frozen=True,
2456
+ init=False,
2457
+ slots=True,
2458
+ )
2459
+ class AsyncComputedVar(ComputedVar[RETURN_TYPE]):
2460
+ """A computed var that wraps a coroutinefunction."""
2461
+
2462
+ _fget: Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]] = (
2463
+ dataclasses.field(default=_default_async_computed_var)
2464
+ )
2465
+
2466
+ @overload
2467
+ def __get__(
2468
+ self: AsyncComputedVar[bool],
2469
+ instance: None,
2470
+ owner: Type,
2471
+ ) -> BooleanVar: ...
2472
+
2473
+ @overload
2474
+ def __get__(
2475
+ self: AsyncComputedVar[int] | ComputedVar[float],
2476
+ instance: None,
2477
+ owner: Type,
2478
+ ) -> NumberVar: ...
2479
+
2480
+ @overload
2481
+ def __get__(
2482
+ self: AsyncComputedVar[str],
2483
+ instance: None,
2484
+ owner: Type,
2485
+ ) -> StringVar: ...
2486
+
2487
+ @overload
2488
+ def __get__(
2489
+ self: AsyncComputedVar[MAPPING_TYPE],
2490
+ instance: None,
2491
+ owner: Type,
2492
+ ) -> ObjectVar[MAPPING_TYPE]: ...
2493
+
2494
+ @overload
2495
+ def __get__(
2496
+ self: AsyncComputedVar[list[LIST_INSIDE]],
2497
+ instance: None,
2498
+ owner: Type,
2499
+ ) -> ArrayVar[list[LIST_INSIDE]]: ...
2500
+
2501
+ @overload
2502
+ def __get__(
2503
+ self: AsyncComputedVar[tuple[LIST_INSIDE, ...]],
2504
+ instance: None,
2505
+ owner: Type,
2506
+ ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ...
2507
+
2508
+ @overload
2509
+ def __get__(
2510
+ self: AsyncComputedVar[BASE_TYPE],
2511
+ instance: None,
2512
+ owner: Type,
2513
+ ) -> ObjectVar[BASE_TYPE]: ...
2514
+
2515
+ @overload
2516
+ def __get__(
2517
+ self: AsyncComputedVar[SQLA_TYPE],
2518
+ instance: None,
2519
+ owner: Type,
2520
+ ) -> ObjectVar[SQLA_TYPE]: ...
2521
+
2522
+ if TYPE_CHECKING:
2523
+
2524
+ @overload
2525
+ def __get__(
2526
+ self: AsyncComputedVar[DATACLASS_TYPE], instance: None, owner: Any
2527
+ ) -> ObjectVar[DATACLASS_TYPE]: ...
2528
+
2529
+ @overload
2530
+ def __get__(self, instance: None, owner: Type) -> AsyncComputedVar[RETURN_TYPE]: ...
2531
+
2532
+ @overload
2533
+ def __get__(
2534
+ self, instance: BaseState, owner: Type
2535
+ ) -> Coroutine[None, None, RETURN_TYPE]: ...
2536
+
2537
+ def __get__(
2538
+ self, instance: BaseState | None, owner
2539
+ ) -> Var | Coroutine[None, None, RETURN_TYPE]:
2540
+ """Get the ComputedVar value.
2541
+
2542
+ If the value is already cached on the instance, return the cached value.
2543
+
2544
+ Args:
2545
+ instance: the instance of the class accessing this computed var.
2546
+ owner: the class that this descriptor is attached to.
2547
+
2548
+ Returns:
2549
+ The value of the var for the given instance.
2550
+ """
2551
+ if instance is None:
2552
+ return super(AsyncComputedVar, self).__get__(instance, owner)
2553
+
2554
+ if not self._cache:
2555
+
2556
+ async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE:
2557
+ value = await self.fget(instance)
2558
+ self._check_deprecated_return_type(instance, value)
2559
+ return value
2560
+
2561
+ return _awaitable_result()
2562
+ else:
2563
+ # handle caching
2564
+ async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE:
2565
+ if not hasattr(instance, self._cache_attr) or self.needs_update(
2566
+ instance
2567
+ ):
2568
+ # Set cache attr on state instance.
2569
+ setattr(instance, self._cache_attr, await self.fget(instance))
2570
+ # Ensure the computed var gets serialized to redis.
2571
+ instance._was_touched = True
2572
+ # Set the last updated timestamp on the state instance.
2573
+ setattr(instance, self._last_updated_attr, datetime.datetime.now())
2574
+ value = getattr(instance, self._cache_attr)
2575
+ self._check_deprecated_return_type(instance, value)
2576
+ return value
2577
+
2578
+ return _awaitable_result()
2579
+
2580
+ @property
2581
+ def fget(self) -> Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]]:
2582
+ """Get the getter function.
2583
+
2584
+ Returns:
2585
+ The getter function.
2586
+ """
2587
+ return self._fget
2588
+
2589
+
2248
2590
  if TYPE_CHECKING:
2249
2591
  BASE_STATE = TypeVar("BASE_STATE", bound=BaseState)
2250
2592
 
@@ -2253,20 +2595,20 @@ if TYPE_CHECKING:
2253
2595
  def computed_var(
2254
2596
  fget: None = None,
2255
2597
  initial_value: Any | types.Unset = types.Unset(),
2256
- cache: bool = False,
2598
+ cache: bool = True,
2257
2599
  deps: Optional[List[Union[str, Var]]] = None,
2258
2600
  auto_deps: bool = True,
2259
2601
  interval: Optional[Union[datetime.timedelta, int]] = None,
2260
2602
  backend: bool | None = None,
2261
2603
  **kwargs,
2262
- ) -> Callable[[Callable[[BASE_STATE], RETURN_TYPE]], ComputedVar[RETURN_TYPE]]: ...
2604
+ ) -> Callable[[Callable[[BASE_STATE], RETURN_TYPE]], ComputedVar[RETURN_TYPE]]: ... # pyright: ignore [reportInvalidTypeVarUse]
2263
2605
 
2264
2606
 
2265
2607
  @overload
2266
2608
  def computed_var(
2267
2609
  fget: Callable[[BASE_STATE], RETURN_TYPE],
2268
2610
  initial_value: RETURN_TYPE | types.Unset = types.Unset(),
2269
- cache: bool = False,
2611
+ cache: bool = True,
2270
2612
  deps: Optional[List[Union[str, Var]]] = None,
2271
2613
  auto_deps: bool = True,
2272
2614
  interval: Optional[Union[datetime.timedelta, int]] = None,
@@ -2278,7 +2620,7 @@ def computed_var(
2278
2620
  def computed_var(
2279
2621
  fget: Callable[[BASE_STATE], Any] | None = None,
2280
2622
  initial_value: Any | types.Unset = types.Unset(),
2281
- cache: Optional[bool] = None,
2623
+ cache: bool = True,
2282
2624
  deps: Optional[List[Union[str, Var]]] = None,
2283
2625
  auto_deps: bool = True,
2284
2626
  interval: Optional[Union[datetime.timedelta, int]] = None,
@@ -2303,16 +2645,8 @@ def computed_var(
2303
2645
  Raises:
2304
2646
  ValueError: If caching is disabled and an update interval is set.
2305
2647
  VarDependencyError: If user supplies dependencies without caching.
2648
+ ComputedVarSignatureError: If the getter function has more than one argument.
2306
2649
  """
2307
- if cache is None:
2308
- cache = False
2309
- console.deprecate(
2310
- "Default non-cached rx.var",
2311
- "the default value will be `@rx.var(cache=True)` in a future release. "
2312
- "To retain uncached var, explicitly pass `@rx.var(cache=False)`",
2313
- deprecation_version="0.6.8",
2314
- removal_version="0.7.0",
2315
- )
2316
2650
  if cache is False and interval is not None:
2317
2651
  raise ValueError("Cannot set update interval without caching.")
2318
2652
 
@@ -2320,10 +2654,31 @@ def computed_var(
2320
2654
  raise VarDependencyError("Cannot track dependencies without caching.")
2321
2655
 
2322
2656
  if fget is not None:
2323
- return ComputedVar(fget, cache=cache)
2657
+ sign = inspect.signature(fget)
2658
+ if len(sign.parameters) != 1:
2659
+ raise ComputedVarSignatureError(fget.__name__, signature=str(sign))
2660
+
2661
+ if inspect.iscoroutinefunction(fget):
2662
+ computed_var_cls = AsyncComputedVar
2663
+ else:
2664
+ computed_var_cls = ComputedVar
2665
+ return computed_var_cls(
2666
+ fget,
2667
+ initial_value=initial_value,
2668
+ cache=cache,
2669
+ deps=deps,
2670
+ auto_deps=auto_deps,
2671
+ interval=interval,
2672
+ backend=backend,
2673
+ **kwargs,
2674
+ )
2324
2675
 
2325
2676
  def wrapper(fget: Callable[[BASE_STATE], Any]) -> ComputedVar:
2326
- return ComputedVar(
2677
+ if inspect.iscoroutinefunction(fget):
2678
+ computed_var_cls = AsyncComputedVar
2679
+ else:
2680
+ computed_var_cls = ComputedVar
2681
+ return computed_var_cls(
2327
2682
  fget,
2328
2683
  initial_value=initial_value,
2329
2684
  cache=cache,
@@ -2392,7 +2747,7 @@ def var_operation_return(
2392
2747
  @dataclasses.dataclass(
2393
2748
  eq=False,
2394
2749
  frozen=True,
2395
- **{"slots": True} if sys.version_info >= (3, 10) else {},
2750
+ slots=True,
2396
2751
  )
2397
2752
  class CustomVarOperation(CachedVarOperation, Var[T]):
2398
2753
  """Base class for custom var operations."""
@@ -2463,7 +2818,7 @@ class NoneVar(Var[None], python_types=type(None)):
2463
2818
  @dataclasses.dataclass(
2464
2819
  eq=False,
2465
2820
  frozen=True,
2466
- **{"slots": True} if sys.version_info >= (3, 10) else {},
2821
+ slots=True,
2467
2822
  )
2468
2823
  class LiteralNoneVar(LiteralVar, NoneVar):
2469
2824
  """A var representing None."""
@@ -2525,7 +2880,7 @@ def get_to_operation(var_subclass: Type[Var]) -> Type[ToOperation]:
2525
2880
  @dataclasses.dataclass(
2526
2881
  eq=False,
2527
2882
  frozen=True,
2528
- **{"slots": True} if sys.version_info >= (3, 10) else {},
2883
+ slots=True,
2529
2884
  )
2530
2885
  class StateOperation(CachedVarOperation, Var):
2531
2886
  """A var operation that accesses a field on an object."""
@@ -2597,7 +2952,7 @@ def get_uuid_string_var() -> Var:
2597
2952
  unique_uuid_var = get_unique_variable_name()
2598
2953
  unique_uuid_var_data = VarData(
2599
2954
  imports={
2600
- f"$/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")}, # type: ignore
2955
+ f"$/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")}, # pyright: ignore [reportArgumentType]
2601
2956
  "react": "useMemo",
2602
2957
  },
2603
2958
  hooks={f"const {unique_uuid_var} = useMemo(generateUUID, [])": None},
@@ -2657,7 +3012,7 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]:
2657
3012
  elif not isinstance(sub, str):
2658
3013
  # Recurse into dict values.
2659
3014
  if hasattr(sub, "values") and callable(sub.values):
2660
- var_datas.extend(_extract_var_data(sub.values()))
3015
+ var_datas.extend(_extract_var_data(sub.values())) # pyright: ignore [reportArgumentType]
2661
3016
  # Recurse into iterable values (or dict keys).
2662
3017
  var_datas.extend(_extract_var_data(sub))
2663
3018
 
@@ -2668,23 +3023,10 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]:
2668
3023
  # Recurse when value is a dict itself.
2669
3024
  values = getattr(value, "values", None)
2670
3025
  if callable(values):
2671
- var_datas.extend(_extract_var_data(values()))
3026
+ var_datas.extend(_extract_var_data(values())) # pyright: ignore [reportArgumentType]
2672
3027
  return var_datas
2673
3028
 
2674
3029
 
2675
- # These names were changed in reflex 0.3.0
2676
- REPLACED_NAMES = {
2677
- "full_name": "_var_full_name",
2678
- "name": "_js_expr",
2679
- "state": "_var_data.state",
2680
- "type_": "_var_type",
2681
- "is_local": "_var_is_local",
2682
- "is_string": "_var_is_string",
2683
- "set_state": "_var_set_state",
2684
- "deps": "_deps",
2685
- }
2686
-
2687
-
2688
3030
  dispatchers: Dict[GenericType, Callable[[Var], Var]] = {}
2689
3031
 
2690
3032
 
@@ -2765,7 +3107,7 @@ def generic_type_to_actual_type_map(
2765
3107
  # call recursively for nested generic types and merge the results
2766
3108
  return {
2767
3109
  k: v
2768
- for generic_arg, actual_arg in zip(generic_args, actual_args)
3110
+ for generic_arg, actual_arg in zip(generic_args, actual_args, strict=True)
2769
3111
  for k, v in generic_type_to_actual_type_map(generic_arg, actual_arg).items()
2770
3112
  }
2771
3113
 
@@ -2922,13 +3264,22 @@ def dispatch(
2922
3264
 
2923
3265
  V = TypeVar("V")
2924
3266
 
2925
- BASE_TYPE = TypeVar("BASE_TYPE", bound=Base)
3267
+ BASE_TYPE = TypeVar("BASE_TYPE", bound=Base | None)
3268
+ SQLA_TYPE = TypeVar("SQLA_TYPE", bound=DeclarativeBase | None)
2926
3269
 
3270
+ if TYPE_CHECKING:
3271
+ from _typeshed import DataclassInstance
3272
+
3273
+ DATACLASS_TYPE = TypeVar("DATACLASS_TYPE", bound=DataclassInstance | None)
3274
+
3275
+ FIELD_TYPE = TypeVar("FIELD_TYPE")
3276
+ MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping | None)
2927
3277
 
2928
- class Field(Generic[T]):
3278
+
3279
+ class Field(Generic[FIELD_TYPE]):
2929
3280
  """Shadow class for Var to allow for type hinting in the IDE."""
2930
3281
 
2931
- def __set__(self, instance, value: T):
3282
+ def __set__(self, instance: Any, value: FIELD_TYPE):
2932
3283
  """Set the Var.
2933
3284
 
2934
3285
  Args:
@@ -2937,41 +3288,55 @@ class Field(Generic[T]):
2937
3288
  """
2938
3289
 
2939
3290
  @overload
2940
- def __get__(self: Field[bool], instance: None, owner) -> BooleanVar: ...
3291
+ def __get__(self: Field[bool], instance: None, owner: Any) -> BooleanVar: ...
2941
3292
 
2942
3293
  @overload
2943
- def __get__(self: Field[int], instance: None, owner) -> NumberVar: ...
3294
+ def __get__(
3295
+ self: Field[int] | Field[float] | Field[int | float], instance: None, owner: Any
3296
+ ) -> NumberVar: ...
2944
3297
 
2945
3298
  @overload
2946
- def __get__(self: Field[str], instance: None, owner) -> StringVar: ...
3299
+ def __get__(self: Field[str], instance: None, owner: Any) -> StringVar: ...
2947
3300
 
2948
3301
  @overload
2949
- def __get__(self: Field[None], instance: None, owner) -> NoneVar: ...
3302
+ def __get__(self: Field[None], instance: None, owner: Any) -> NoneVar: ...
2950
3303
 
2951
3304
  @overload
2952
3305
  def __get__(
2953
3306
  self: Field[List[V]] | Field[Set[V]] | Field[Tuple[V, ...]],
2954
3307
  instance: None,
2955
- owner,
3308
+ owner: Any,
2956
3309
  ) -> ArrayVar[List[V]]: ...
2957
3310
 
2958
3311
  @overload
2959
3312
  def __get__(
2960
- self: Field[Dict[str, V]], instance: None, owner
2961
- ) -> ObjectVar[Dict[str, V]]: ...
3313
+ self: Field[MAPPING_TYPE], instance: None, owner: Any
3314
+ ) -> ObjectVar[MAPPING_TYPE]: ...
2962
3315
 
2963
3316
  @overload
2964
3317
  def __get__(
2965
- self: Field[BASE_TYPE], instance: None, owner
3318
+ self: Field[BASE_TYPE], instance: None, owner: Any
2966
3319
  ) -> ObjectVar[BASE_TYPE]: ...
2967
3320
 
2968
3321
  @overload
2969
- def __get__(self, instance: None, owner) -> Var[T]: ...
3322
+ def __get__(
3323
+ self: Field[SQLA_TYPE], instance: None, owner: Any
3324
+ ) -> ObjectVar[SQLA_TYPE]: ...
3325
+
3326
+ if TYPE_CHECKING:
3327
+
3328
+ @overload
3329
+ def __get__(
3330
+ self: Field[DATACLASS_TYPE], instance: None, owner: Any
3331
+ ) -> ObjectVar[DATACLASS_TYPE]: ...
3332
+
3333
+ @overload
3334
+ def __get__(self, instance: None, owner: Any) -> Var[FIELD_TYPE]: ...
2970
3335
 
2971
3336
  @overload
2972
- def __get__(self, instance, owner) -> T: ...
3337
+ def __get__(self, instance: Any, owner: Any) -> FIELD_TYPE: ...
2973
3338
 
2974
- def __get__(self, instance, owner): # type: ignore
3339
+ def __get__(self, instance: Any, owner: Any): # pyright: ignore [reportInconsistentOverload]
2975
3340
  """Get the Var.
2976
3341
 
2977
3342
  Args:
@@ -2980,7 +3345,7 @@ class Field(Generic[T]):
2980
3345
  """
2981
3346
 
2982
3347
 
2983
- def field(value: T) -> Field[T]:
3348
+ def field(value: FIELD_TYPE) -> Field[FIELD_TYPE]:
2984
3349
  """Create a Field with a value.
2985
3350
 
2986
3351
  Args:
@@ -2989,4 +3354,4 @@ def field(value: T) -> Field[T]:
2989
3354
  Returns:
2990
3355
  The Field.
2991
3356
  """
2992
- return value # type: ignore
3357
+ return value # pyright: ignore [reportReturnType]