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/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[DICT_KEY, DICT_VAL]],
2006
2223
  instance: None,
2007
2224
  owner: Type,
2008
- ) -> ObjectVar[dict[DICT_KEY, DICT_VAL]]: ...
2225
+ ) -> ObjectVar[Mapping[DICT_KEY, DICT_VAL]]: ...
2009
2226
 
2010
2227
  @overload
2011
2228
  def __get__(
@@ -2014,13 +2231,6 @@ class ComputedVar(Var[RETURN_TYPE]):
2014
2231
  owner: Type,
2015
2232
  ) -> ArrayVar[list[LIST_INSIDE]]: ...
2016
2233
 
2017
- @overload
2018
- def __get__(
2019
- self: ComputedVar[set[LIST_INSIDE]],
2020
- instance: None,
2021
- owner: Type,
2022
- ) -> ArrayVar[set[LIST_INSIDE]]: ...
2023
-
2024
2234
  @overload
2025
2235
  def __get__(
2026
2236
  self: ComputedVar[tuple[LIST_INSIDE, ...]],
@@ -2034,7 +2244,7 @@ class ComputedVar(Var[RETURN_TYPE]):
2034
2244
  @overload
2035
2245
  def __get__(self, instance: BaseState, owner: Type) -> RETURN_TYPE: ...
2036
2246
 
2037
- def __get__(self, instance: BaseState | None, owner):
2247
+ def __get__(self, instance: BaseState | None, owner: Type):
2038
2248
  """Get the ComputedVar value.
2039
2249
 
2040
2250
  If the value is already cached on the instance, return the cached value.
@@ -2077,130 +2287,69 @@ class ComputedVar(Var[RETURN_TYPE]):
2077
2287
  setattr(instance, self._last_updated_attr, datetime.datetime.now())
2078
2288
  value = getattr(instance, self._cache_attr)
2079
2289
 
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
- )
2290
+ self._check_deprecated_return_type(instance, value)
2088
2291
 
2089
2292
  return value
2090
2293
 
2294
+ def _check_deprecated_return_type(self, instance: BaseState, value: Any) -> None:
2295
+ if not _isinstance(value, self._var_type):
2296
+ console.error(
2297
+ f"Computed var '{type(instance).__name__}.{self._js_expr}' must return"
2298
+ f" type '{self._var_type}', got '{type(value)}'."
2299
+ )
2300
+
2091
2301
  def _deps(
2092
2302
  self,
2093
- objclass: Type,
2303
+ objclass: Type[BaseState],
2094
2304
  obj: FunctionType | CodeType | None = None,
2095
- self_name: Optional[str] = None,
2096
- ) -> set[str]:
2305
+ ) -> dict[str, set[str]]:
2097
2306
  """Determine var dependencies of this ComputedVar.
2098
2307
 
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".
2308
+ Save references to attributes accessed on "self" or other fetched states.
2309
+
2310
+ Recursively called when the function makes a method call on "self" or
2311
+ define comprehensions or nested functions that may reference "self".
2102
2312
 
2103
2313
  Args:
2104
2314
  objclass: the class obj this ComputedVar is attached to.
2105
2315
  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
2316
 
2108
2317
  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).
2318
+ A dictionary mapping state names to the set of variable names
2319
+ accessed by the given obj.
2114
2320
  """
2321
+ from .dep_tracking import DependencyTracker
2322
+
2323
+ d = {}
2324
+ if self._static_deps:
2325
+ d.update(self._static_deps)
2326
+ # None is a placeholder for the current state class.
2327
+ if None in d:
2328
+ d[objclass.get_full_name()] = d.pop(None)
2329
+
2115
2330
  if not self._auto_deps:
2116
- return self._static_deps
2117
- d = self._static_deps.copy()
2331
+ return d
2332
+
2118
2333
  if obj is None:
2119
2334
  fget = self._fget
2120
2335
  if fget is not None:
2121
2336
  obj = cast(FunctionType, fget)
2122
2337
  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
2338
+ return d
2130
2339
 
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
2340
+ try:
2341
+ return DependencyTracker(
2342
+ func=obj, state_cls=objclass, dependencies=d
2343
+ ).dependencies
2344
+ except Exception as e:
2345
+ console.warn(
2346
+ "Failed to automatically determine dependencies for computed var "
2347
+ f"{objclass.__name__}.{self._js_expr}: {e}. "
2348
+ "Provide static_deps and set auto_deps=False to suppress this warning."
2349
+ )
2350
+ return d
2202
2351
 
2203
- def mark_dirty(self, instance) -> None:
2352
+ def mark_dirty(self, instance: BaseState) -> None:
2204
2353
  """Mark this ComputedVar as dirty.
2205
2354
 
2206
2355
  Args:
@@ -2209,6 +2358,37 @@ class ComputedVar(Var[RETURN_TYPE]):
2209
2358
  with contextlib.suppress(AttributeError):
2210
2359
  delattr(instance, self._cache_attr)
2211
2360
 
2361
+ def add_dependency(self, objclass: Type[BaseState], dep: Var):
2362
+ """Explicitly add a dependency to the ComputedVar.
2363
+
2364
+ After adding the dependency, when the `dep` changes, this computed var
2365
+ will be marked dirty.
2366
+
2367
+ Args:
2368
+ objclass: The class obj this ComputedVar is attached to.
2369
+ dep: The dependency to add.
2370
+
2371
+ Raises:
2372
+ VarDependencyError: If the dependency is not a Var instance with a
2373
+ state and field name
2374
+ """
2375
+ if all_var_data := dep._get_all_var_data():
2376
+ state_name = all_var_data.state
2377
+ if state_name:
2378
+ var_name = all_var_data.field_name
2379
+ if var_name:
2380
+ self._static_deps.setdefault(state_name, set()).add(var_name)
2381
+ objclass.get_root_state().get_class_substate(
2382
+ state_name
2383
+ )._var_dependencies.setdefault(var_name, set()).add(
2384
+ (objclass.get_full_name(), self._js_expr)
2385
+ )
2386
+ return
2387
+ raise VarDependencyError(
2388
+ "ComputedVar dependencies must be Var instances with a state and "
2389
+ f"field name, got {dep!r}."
2390
+ )
2391
+
2212
2392
  def _determine_var_type(self) -> Type:
2213
2393
  """Get the type of the var.
2214
2394
 
@@ -2218,7 +2398,7 @@ class ComputedVar(Var[RETURN_TYPE]):
2218
2398
  hints = get_type_hints(self._fget)
2219
2399
  if "return" in hints:
2220
2400
  return hints["return"]
2221
- return Any
2401
+ return Any # pyright: ignore [reportReturnType]
2222
2402
 
2223
2403
  @property
2224
2404
  def __class__(self) -> Type:
@@ -2245,6 +2425,126 @@ class DynamicRouteVar(ComputedVar[Union[str, List[str]]]):
2245
2425
  pass
2246
2426
 
2247
2427
 
2428
+ async def _default_async_computed_var(_self: BaseState) -> Any:
2429
+ return None
2430
+
2431
+
2432
+ @dataclasses.dataclass(
2433
+ eq=False,
2434
+ frozen=True,
2435
+ init=False,
2436
+ slots=True,
2437
+ )
2438
+ class AsyncComputedVar(ComputedVar[RETURN_TYPE]):
2439
+ """A computed var that wraps a coroutinefunction."""
2440
+
2441
+ _fget: Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]] = (
2442
+ dataclasses.field(default=_default_async_computed_var)
2443
+ )
2444
+
2445
+ @overload
2446
+ def __get__(
2447
+ self: AsyncComputedVar[bool],
2448
+ instance: None,
2449
+ owner: Type,
2450
+ ) -> BooleanVar: ...
2451
+
2452
+ @overload
2453
+ def __get__(
2454
+ self: AsyncComputedVar[int] | ComputedVar[float],
2455
+ instance: None,
2456
+ owner: Type,
2457
+ ) -> NumberVar: ...
2458
+
2459
+ @overload
2460
+ def __get__(
2461
+ self: AsyncComputedVar[str],
2462
+ instance: None,
2463
+ owner: Type,
2464
+ ) -> StringVar: ...
2465
+
2466
+ @overload
2467
+ def __get__(
2468
+ self: AsyncComputedVar[Mapping[DICT_KEY, DICT_VAL]],
2469
+ instance: None,
2470
+ owner: Type,
2471
+ ) -> ObjectVar[Mapping[DICT_KEY, DICT_VAL]]: ...
2472
+
2473
+ @overload
2474
+ def __get__(
2475
+ self: AsyncComputedVar[list[LIST_INSIDE]],
2476
+ instance: None,
2477
+ owner: Type,
2478
+ ) -> ArrayVar[list[LIST_INSIDE]]: ...
2479
+
2480
+ @overload
2481
+ def __get__(
2482
+ self: AsyncComputedVar[tuple[LIST_INSIDE, ...]],
2483
+ instance: None,
2484
+ owner: Type,
2485
+ ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ...
2486
+
2487
+ @overload
2488
+ def __get__(self, instance: None, owner: Type) -> AsyncComputedVar[RETURN_TYPE]: ...
2489
+
2490
+ @overload
2491
+ def __get__(
2492
+ self, instance: BaseState, owner: Type
2493
+ ) -> Coroutine[None, None, RETURN_TYPE]: ...
2494
+
2495
+ def __get__(
2496
+ self, instance: BaseState | None, owner
2497
+ ) -> Var | Coroutine[None, None, RETURN_TYPE]:
2498
+ """Get the ComputedVar value.
2499
+
2500
+ If the value is already cached on the instance, return the cached value.
2501
+
2502
+ Args:
2503
+ instance: the instance of the class accessing this computed var.
2504
+ owner: the class that this descriptor is attached to.
2505
+
2506
+ Returns:
2507
+ The value of the var for the given instance.
2508
+ """
2509
+ if instance is None:
2510
+ return super(AsyncComputedVar, self).__get__(instance, owner)
2511
+
2512
+ if not self._cache:
2513
+
2514
+ async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE:
2515
+ value = await self.fget(instance)
2516
+ self._check_deprecated_return_type(instance, value)
2517
+ return value
2518
+
2519
+ return _awaitable_result()
2520
+ else:
2521
+ # handle caching
2522
+ async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE:
2523
+ if not hasattr(instance, self._cache_attr) or self.needs_update(
2524
+ instance
2525
+ ):
2526
+ # Set cache attr on state instance.
2527
+ setattr(instance, self._cache_attr, await self.fget(instance))
2528
+ # Ensure the computed var gets serialized to redis.
2529
+ instance._was_touched = True
2530
+ # Set the last updated timestamp on the state instance.
2531
+ setattr(instance, self._last_updated_attr, datetime.datetime.now())
2532
+ value = getattr(instance, self._cache_attr)
2533
+ self._check_deprecated_return_type(instance, value)
2534
+ return value
2535
+
2536
+ return _awaitable_result()
2537
+
2538
+ @property
2539
+ def fget(self) -> Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]]:
2540
+ """Get the getter function.
2541
+
2542
+ Returns:
2543
+ The getter function.
2544
+ """
2545
+ return self._fget
2546
+
2547
+
2248
2548
  if TYPE_CHECKING:
2249
2549
  BASE_STATE = TypeVar("BASE_STATE", bound=BaseState)
2250
2550
 
@@ -2253,20 +2553,20 @@ if TYPE_CHECKING:
2253
2553
  def computed_var(
2254
2554
  fget: None = None,
2255
2555
  initial_value: Any | types.Unset = types.Unset(),
2256
- cache: bool = False,
2556
+ cache: bool = True,
2257
2557
  deps: Optional[List[Union[str, Var]]] = None,
2258
2558
  auto_deps: bool = True,
2259
2559
  interval: Optional[Union[datetime.timedelta, int]] = None,
2260
2560
  backend: bool | None = None,
2261
2561
  **kwargs,
2262
- ) -> Callable[[Callable[[BASE_STATE], RETURN_TYPE]], ComputedVar[RETURN_TYPE]]: ...
2562
+ ) -> Callable[[Callable[[BASE_STATE], RETURN_TYPE]], ComputedVar[RETURN_TYPE]]: ... # pyright: ignore [reportInvalidTypeVarUse]
2263
2563
 
2264
2564
 
2265
2565
  @overload
2266
2566
  def computed_var(
2267
2567
  fget: Callable[[BASE_STATE], RETURN_TYPE],
2268
2568
  initial_value: RETURN_TYPE | types.Unset = types.Unset(),
2269
- cache: bool = False,
2569
+ cache: bool = True,
2270
2570
  deps: Optional[List[Union[str, Var]]] = None,
2271
2571
  auto_deps: bool = True,
2272
2572
  interval: Optional[Union[datetime.timedelta, int]] = None,
@@ -2278,7 +2578,7 @@ def computed_var(
2278
2578
  def computed_var(
2279
2579
  fget: Callable[[BASE_STATE], Any] | None = None,
2280
2580
  initial_value: Any | types.Unset = types.Unset(),
2281
- cache: Optional[bool] = None,
2581
+ cache: bool = True,
2282
2582
  deps: Optional[List[Union[str, Var]]] = None,
2283
2583
  auto_deps: bool = True,
2284
2584
  interval: Optional[Union[datetime.timedelta, int]] = None,
@@ -2303,16 +2603,8 @@ def computed_var(
2303
2603
  Raises:
2304
2604
  ValueError: If caching is disabled and an update interval is set.
2305
2605
  VarDependencyError: If user supplies dependencies without caching.
2606
+ ComputedVarSignatureError: If the getter function has more than one argument.
2306
2607
  """
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
2608
  if cache is False and interval is not None:
2317
2609
  raise ValueError("Cannot set update interval without caching.")
2318
2610
 
@@ -2320,10 +2612,31 @@ def computed_var(
2320
2612
  raise VarDependencyError("Cannot track dependencies without caching.")
2321
2613
 
2322
2614
  if fget is not None:
2323
- return ComputedVar(fget, cache=cache)
2615
+ sign = inspect.signature(fget)
2616
+ if len(sign.parameters) != 1:
2617
+ raise ComputedVarSignatureError(fget.__name__, signature=str(sign))
2618
+
2619
+ if inspect.iscoroutinefunction(fget):
2620
+ computed_var_cls = AsyncComputedVar
2621
+ else:
2622
+ computed_var_cls = ComputedVar
2623
+ return computed_var_cls(
2624
+ fget,
2625
+ initial_value=initial_value,
2626
+ cache=cache,
2627
+ deps=deps,
2628
+ auto_deps=auto_deps,
2629
+ interval=interval,
2630
+ backend=backend,
2631
+ **kwargs,
2632
+ )
2324
2633
 
2325
2634
  def wrapper(fget: Callable[[BASE_STATE], Any]) -> ComputedVar:
2326
- return ComputedVar(
2635
+ if inspect.iscoroutinefunction(fget):
2636
+ computed_var_cls = AsyncComputedVar
2637
+ else:
2638
+ computed_var_cls = ComputedVar
2639
+ return computed_var_cls(
2327
2640
  fget,
2328
2641
  initial_value=initial_value,
2329
2642
  cache=cache,
@@ -2392,7 +2705,7 @@ def var_operation_return(
2392
2705
  @dataclasses.dataclass(
2393
2706
  eq=False,
2394
2707
  frozen=True,
2395
- **{"slots": True} if sys.version_info >= (3, 10) else {},
2708
+ slots=True,
2396
2709
  )
2397
2710
  class CustomVarOperation(CachedVarOperation, Var[T]):
2398
2711
  """Base class for custom var operations."""
@@ -2463,7 +2776,7 @@ class NoneVar(Var[None], python_types=type(None)):
2463
2776
  @dataclasses.dataclass(
2464
2777
  eq=False,
2465
2778
  frozen=True,
2466
- **{"slots": True} if sys.version_info >= (3, 10) else {},
2779
+ slots=True,
2467
2780
  )
2468
2781
  class LiteralNoneVar(LiteralVar, NoneVar):
2469
2782
  """A var representing None."""
@@ -2525,7 +2838,7 @@ def get_to_operation(var_subclass: Type[Var]) -> Type[ToOperation]:
2525
2838
  @dataclasses.dataclass(
2526
2839
  eq=False,
2527
2840
  frozen=True,
2528
- **{"slots": True} if sys.version_info >= (3, 10) else {},
2841
+ slots=True,
2529
2842
  )
2530
2843
  class StateOperation(CachedVarOperation, Var):
2531
2844
  """A var operation that accesses a field on an object."""
@@ -2597,7 +2910,7 @@ def get_uuid_string_var() -> Var:
2597
2910
  unique_uuid_var = get_unique_variable_name()
2598
2911
  unique_uuid_var_data = VarData(
2599
2912
  imports={
2600
- f"$/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")}, # type: ignore
2913
+ f"$/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")}, # pyright: ignore [reportArgumentType]
2601
2914
  "react": "useMemo",
2602
2915
  },
2603
2916
  hooks={f"const {unique_uuid_var} = useMemo(generateUUID, [])": None},
@@ -2657,7 +2970,7 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]:
2657
2970
  elif not isinstance(sub, str):
2658
2971
  # Recurse into dict values.
2659
2972
  if hasattr(sub, "values") and callable(sub.values):
2660
- var_datas.extend(_extract_var_data(sub.values()))
2973
+ var_datas.extend(_extract_var_data(sub.values())) # pyright: ignore [reportArgumentType]
2661
2974
  # Recurse into iterable values (or dict keys).
2662
2975
  var_datas.extend(_extract_var_data(sub))
2663
2976
 
@@ -2668,23 +2981,10 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]:
2668
2981
  # Recurse when value is a dict itself.
2669
2982
  values = getattr(value, "values", None)
2670
2983
  if callable(values):
2671
- var_datas.extend(_extract_var_data(values()))
2984
+ var_datas.extend(_extract_var_data(values())) # pyright: ignore [reportArgumentType]
2672
2985
  return var_datas
2673
2986
 
2674
2987
 
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
2988
  dispatchers: Dict[GenericType, Callable[[Var], Var]] = {}
2689
2989
 
2690
2990
 
@@ -2765,7 +3065,7 @@ def generic_type_to_actual_type_map(
2765
3065
  # call recursively for nested generic types and merge the results
2766
3066
  return {
2767
3067
  k: v
2768
- for generic_arg, actual_arg in zip(generic_args, actual_args)
3068
+ for generic_arg, actual_arg in zip(generic_args, actual_args, strict=True)
2769
3069
  for k, v in generic_type_to_actual_type_map(generic_arg, actual_arg).items()
2770
3070
  }
2771
3071
 
@@ -2922,13 +3222,22 @@ def dispatch(
2922
3222
 
2923
3223
  V = TypeVar("V")
2924
3224
 
2925
- BASE_TYPE = TypeVar("BASE_TYPE", bound=Base)
3225
+ BASE_TYPE = TypeVar("BASE_TYPE", bound=Base | None)
3226
+ SQLA_TYPE = TypeVar("SQLA_TYPE", bound=DeclarativeBase | None)
2926
3227
 
3228
+ if TYPE_CHECKING:
3229
+ from _typeshed import DataclassInstance
2927
3230
 
2928
- class Field(Generic[T]):
3231
+ DATACLASS_TYPE = TypeVar("DATACLASS_TYPE", bound=DataclassInstance | None)
3232
+
3233
+ FIELD_TYPE = TypeVar("FIELD_TYPE")
3234
+ MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping | None)
3235
+
3236
+
3237
+ class Field(Generic[FIELD_TYPE]):
2929
3238
  """Shadow class for Var to allow for type hinting in the IDE."""
2930
3239
 
2931
- def __set__(self, instance, value: T):
3240
+ def __set__(self, instance: Any, value: FIELD_TYPE):
2932
3241
  """Set the Var.
2933
3242
 
2934
3243
  Args:
@@ -2937,41 +3246,55 @@ class Field(Generic[T]):
2937
3246
  """
2938
3247
 
2939
3248
  @overload
2940
- def __get__(self: Field[bool], instance: None, owner) -> BooleanVar: ...
3249
+ def __get__(self: Field[bool], instance: None, owner: Any) -> BooleanVar: ...
2941
3250
 
2942
3251
  @overload
2943
- def __get__(self: Field[int], instance: None, owner) -> NumberVar: ...
3252
+ def __get__(
3253
+ self: Field[int] | Field[float] | Field[int | float], instance: None, owner: Any
3254
+ ) -> NumberVar: ...
2944
3255
 
2945
3256
  @overload
2946
- def __get__(self: Field[str], instance: None, owner) -> StringVar: ...
3257
+ def __get__(self: Field[str], instance: None, owner: Any) -> StringVar: ...
2947
3258
 
2948
3259
  @overload
2949
- def __get__(self: Field[None], instance: None, owner) -> NoneVar: ...
3260
+ def __get__(self: Field[None], instance: None, owner: Any) -> NoneVar: ...
2950
3261
 
2951
3262
  @overload
2952
3263
  def __get__(
2953
3264
  self: Field[List[V]] | Field[Set[V]] | Field[Tuple[V, ...]],
2954
3265
  instance: None,
2955
- owner,
3266
+ owner: Any,
2956
3267
  ) -> ArrayVar[List[V]]: ...
2957
3268
 
2958
3269
  @overload
2959
3270
  def __get__(
2960
- self: Field[Dict[str, V]], instance: None, owner
2961
- ) -> ObjectVar[Dict[str, V]]: ...
3271
+ self: Field[MAPPING_TYPE], instance: None, owner: Any
3272
+ ) -> ObjectVar[MAPPING_TYPE]: ...
2962
3273
 
2963
3274
  @overload
2964
3275
  def __get__(
2965
- self: Field[BASE_TYPE], instance: None, owner
3276
+ self: Field[BASE_TYPE], instance: None, owner: Any
2966
3277
  ) -> ObjectVar[BASE_TYPE]: ...
2967
3278
 
2968
3279
  @overload
2969
- def __get__(self, instance: None, owner) -> Var[T]: ...
3280
+ def __get__(
3281
+ self: Field[SQLA_TYPE], instance: None, owner: Any
3282
+ ) -> ObjectVar[SQLA_TYPE]: ...
3283
+
3284
+ if TYPE_CHECKING:
3285
+
3286
+ @overload
3287
+ def __get__(
3288
+ self: Field[DATACLASS_TYPE], instance: None, owner: Any
3289
+ ) -> ObjectVar[DATACLASS_TYPE]: ...
3290
+
3291
+ @overload
3292
+ def __get__(self, instance: None, owner: Any) -> Var[FIELD_TYPE]: ...
2970
3293
 
2971
3294
  @overload
2972
- def __get__(self, instance, owner) -> T: ...
3295
+ def __get__(self, instance: Any, owner: Any) -> FIELD_TYPE: ...
2973
3296
 
2974
- def __get__(self, instance, owner): # type: ignore
3297
+ def __get__(self, instance: Any, owner: Any): # pyright: ignore [reportInconsistentOverload]
2975
3298
  """Get the Var.
2976
3299
 
2977
3300
  Args:
@@ -2980,7 +3303,7 @@ class Field(Generic[T]):
2980
3303
  """
2981
3304
 
2982
3305
 
2983
- def field(value: T) -> Field[T]:
3306
+ def field(value: FIELD_TYPE) -> Field[FIELD_TYPE]:
2984
3307
  """Create a Field with a value.
2985
3308
 
2986
3309
  Args:
@@ -2989,4 +3312,4 @@ def field(value: T) -> Field[T]:
2989
3312
  Returns:
2990
3313
  The Field.
2991
3314
  """
2992
- return value # type: ignore
3315
+ return value # pyright: ignore [reportReturnType]