reflex 0.6.8a1__py3-none-any.whl → 0.7.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of reflex might be problematic. Click here for more details.

Files changed (155) 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 +2 -2
  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 +244 -115
  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/bare.py +1 -1
  16. reflex/components/base/error_boundary.py +2 -1
  17. reflex/components/base/error_boundary.pyi +2 -1
  18. reflex/components/base/meta.py +2 -2
  19. reflex/components/base/strict_mode.py +10 -0
  20. reflex/components/base/strict_mode.pyi +57 -0
  21. reflex/components/component.py +38 -77
  22. reflex/components/core/banner.py +83 -4
  23. reflex/components/core/banner.pyi +86 -0
  24. reflex/components/core/breakpoints.py +3 -1
  25. reflex/components/core/client_side_routing.py +1 -1
  26. reflex/components/core/client_side_routing.pyi +1 -1
  27. reflex/components/core/cond.py +9 -10
  28. reflex/components/core/debounce.py +1 -1
  29. reflex/components/core/foreach.py +23 -3
  30. reflex/components/core/html.py +1 -1
  31. reflex/components/core/match.py +5 -5
  32. reflex/components/core/sticky.py +160 -0
  33. reflex/components/core/sticky.pyi +449 -0
  34. reflex/components/core/upload.py +2 -2
  35. reflex/components/datadisplay/code.py +5 -14
  36. reflex/components/datadisplay/dataeditor.py +7 -4
  37. reflex/components/datadisplay/logo.py +13 -8
  38. reflex/components/datadisplay/shiki_code_block.py +14 -9
  39. reflex/components/dynamic.py +22 -3
  40. reflex/components/el/constants/reflex.py +1 -1
  41. reflex/components/el/element.py +1 -1
  42. reflex/components/el/elements/forms.py +4 -4
  43. reflex/components/el/elements/forms.pyi +4 -4
  44. reflex/components/lucide/icon.py +46 -8
  45. reflex/components/lucide/icon.pyi +54 -0
  46. reflex/components/markdown/markdown.py +10 -8
  47. reflex/components/moment/moment.py +2 -2
  48. reflex/components/next/image.py +16 -4
  49. reflex/components/next/image.pyi +4 -2
  50. reflex/components/next/link.py +1 -1
  51. reflex/components/plotly/plotly.py +5 -5
  52. reflex/components/props.py +3 -3
  53. reflex/components/radix/__init__.pyi +1 -1
  54. reflex/components/radix/primitives/accordion.py +9 -5
  55. reflex/components/radix/primitives/accordion.pyi +3 -1
  56. reflex/components/radix/primitives/drawer.py +5 -2
  57. reflex/components/radix/primitives/drawer.pyi +4 -4
  58. reflex/components/radix/primitives/form.pyi +6 -6
  59. reflex/components/radix/primitives/progress.py +1 -1
  60. reflex/components/radix/primitives/slider.py +1 -1
  61. reflex/components/radix/themes/color_mode.py +11 -9
  62. reflex/components/radix/themes/components/alert_dialog.py +3 -0
  63. reflex/components/radix/themes/components/card.py +1 -1
  64. reflex/components/radix/themes/components/card.pyi +1 -1
  65. reflex/components/radix/themes/components/context_menu.py +5 -0
  66. reflex/components/radix/themes/components/dialog.py +3 -0
  67. reflex/components/radix/themes/components/dropdown_menu.py +5 -0
  68. reflex/components/radix/themes/components/hover_card.py +3 -0
  69. reflex/components/radix/themes/components/icon_button.py +2 -2
  70. reflex/components/radix/themes/components/icon_button.pyi +1 -0
  71. reflex/components/radix/themes/components/popover.py +3 -0
  72. reflex/components/radix/themes/components/radio_cards.py +2 -0
  73. reflex/components/radix/themes/components/radio_group.py +1 -1
  74. reflex/components/radix/themes/components/select.py +3 -0
  75. reflex/components/radix/themes/components/tabs.py +3 -0
  76. reflex/components/radix/themes/components/text_area.py +12 -0
  77. reflex/components/radix/themes/components/text_area.pyi +2 -0
  78. reflex/components/radix/themes/components/text_field.py +1 -1
  79. reflex/components/radix/themes/components/tooltip.py +3 -1
  80. reflex/components/radix/themes/components/tooltip.pyi +1 -0
  81. reflex/components/radix/themes/layout/__init__.pyi +1 -1
  82. reflex/components/radix/themes/layout/list.py +2 -2
  83. reflex/components/radix/themes/layout/stack.py +2 -2
  84. reflex/components/radix/themes/typography/link.py +1 -1
  85. reflex/components/radix/themes/typography/text.py +2 -2
  86. reflex/components/react_player/react_player.py +1 -1
  87. reflex/components/recharts/__init__.py +2 -0
  88. reflex/components/recharts/__init__.pyi +2 -0
  89. reflex/components/recharts/charts.py +15 -15
  90. reflex/components/recharts/general.py +19 -4
  91. reflex/components/recharts/general.pyi +55 -4
  92. reflex/components/recharts/polar.py +2 -2
  93. reflex/components/recharts/recharts.py +4 -4
  94. reflex/components/sonner/toast.py +15 -13
  95. reflex/components/sonner/toast.pyi +6 -6
  96. reflex/components/suneditor/editor.py +6 -4
  97. reflex/components/suneditor/editor.pyi +2 -2
  98. reflex/components/tags/iter_tag.py +3 -3
  99. reflex/components/tags/tag.py +25 -3
  100. reflex/config.py +48 -20
  101. reflex/constants/__init__.py +1 -0
  102. reflex/constants/base.py +4 -1
  103. reflex/constants/compiler.py +5 -2
  104. reflex/constants/config.py +8 -1
  105. reflex/constants/installer.py +9 -9
  106. reflex/constants/style.py +1 -1
  107. reflex/custom_components/custom_components.py +9 -7
  108. reflex/event.py +137 -163
  109. reflex/experimental/__init__.py +19 -11
  110. reflex/experimental/client_state.py +53 -28
  111. reflex/experimental/hooks.py +5 -5
  112. reflex/experimental/layout.py +8 -5
  113. reflex/experimental/layout.pyi +1 -1
  114. reflex/experimental/misc.py +3 -3
  115. reflex/istate/wrappers.py +1 -1
  116. reflex/middleware/hydrate_middleware.py +2 -2
  117. reflex/model.py +11 -6
  118. reflex/page.py +3 -3
  119. reflex/reflex.py +90 -19
  120. reflex/route.py +1 -1
  121. reflex/state.py +358 -401
  122. reflex/style.py +27 -3
  123. reflex/testing.py +34 -39
  124. reflex/utils/build.py +6 -2
  125. reflex/utils/codespaces.py +1 -4
  126. reflex/utils/compat.py +6 -5
  127. reflex/utils/console.py +52 -21
  128. reflex/utils/exceptions.py +76 -26
  129. reflex/utils/exec.py +69 -74
  130. reflex/utils/export.py +6 -1
  131. reflex/utils/format.py +7 -39
  132. reflex/utils/imports.py +2 -2
  133. reflex/utils/lazy_loader.py +7 -1
  134. reflex/utils/path_ops.py +28 -14
  135. reflex/utils/prerequisites.py +324 -65
  136. reflex/utils/processes.py +45 -32
  137. reflex/utils/pyi_generator.py +30 -25
  138. reflex/utils/registry.py +4 -4
  139. reflex/utils/serializers.py +1 -1
  140. reflex/utils/telemetry.py +5 -4
  141. reflex/utils/types.py +42 -18
  142. reflex/vars/base.py +650 -333
  143. reflex/vars/datetime.py +6 -7
  144. reflex/vars/dep_tracking.py +344 -0
  145. reflex/vars/function.py +11 -5
  146. reflex/vars/number.py +31 -43
  147. reflex/vars/object.py +63 -62
  148. reflex/vars/sequence.py +79 -67
  149. {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/METADATA +7 -10
  150. {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/RECORD +153 -150
  151. {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/WHEEL +1 -1
  152. reflex/experimental/assets.py +0 -37
  153. reflex/proxy.py +0 -119
  154. {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/LICENSE +0 -0
  155. {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.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,10 @@ 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
+ UntypedComputedVarError,
48
52
  VarAttributeError,
49
53
  VarDependencyError,
50
54
  VarTypeError,
51
- VarValueError,
52
55
  )
53
56
  from reflex.utils.format import format_state_name
54
57
  from reflex.utils.imports import (
@@ -64,6 +67,7 @@ from reflex.utils.types import (
64
67
  _isinstance,
65
68
  get_origin,
66
69
  has_args,
70
+ safe_issubclass,
67
71
  unionize,
68
72
  )
69
73
 
@@ -77,6 +81,8 @@ if TYPE_CHECKING:
77
81
 
78
82
  VAR_TYPE = TypeVar("VAR_TYPE", covariant=True)
79
83
  OTHER_VAR_TYPE = TypeVar("OTHER_VAR_TYPE")
84
+ STRING_T = TypeVar("STRING_T", bound=str)
85
+ SEQUENCE_TYPE = TypeVar("SEQUENCE_TYPE", bound=Sequence)
80
86
 
81
87
  warnings.filterwarnings("ignore", message="fields may not start with an underscore")
82
88
 
@@ -127,7 +133,7 @@ class VarData:
127
133
  state: str = "",
128
134
  field_name: str = "",
129
135
  imports: ImportDict | ParsedImportDict | None = None,
130
- hooks: dict[str, VarData | None] | None = None,
136
+ hooks: Mapping[str, VarData | None] | Sequence[str] | str | None = None,
131
137
  deps: list[Var] | None = None,
132
138
  position: Hooks.HookPosition | None = None,
133
139
  ):
@@ -141,10 +147,12 @@ class VarData:
141
147
  deps: Dependencies of the var for useCallback.
142
148
  position: Position of the hook in the component.
143
149
  """
150
+ if isinstance(hooks, str):
151
+ hooks = [hooks]
152
+ if not isinstance(hooks, dict):
153
+ hooks = {hook: None for hook in (hooks or [])}
144
154
  immutable_imports: ImmutableParsedImportDict = tuple(
145
- sorted(
146
- ((k, tuple(sorted(v))) for k, v in parse_imports(imports or {}).items())
147
- )
155
+ (k, tuple(v)) for k, v in parse_imports(imports or {}).items()
148
156
  )
149
157
  object.__setattr__(self, "state", state)
150
158
  object.__setattr__(self, "field_name", field_name)
@@ -153,6 +161,16 @@ class VarData:
153
161
  object.__setattr__(self, "deps", tuple(deps or []))
154
162
  object.__setattr__(self, "position", position or None)
155
163
 
164
+ if hooks and any(hooks.values()):
165
+ merged_var_data = VarData.merge(self, *hooks.values())
166
+ if merged_var_data is not None:
167
+ object.__setattr__(self, "state", merged_var_data.state)
168
+ object.__setattr__(self, "field_name", merged_var_data.field_name)
169
+ object.__setattr__(self, "imports", merged_var_data.imports)
170
+ object.__setattr__(self, "hooks", merged_var_data.hooks)
171
+ object.__setattr__(self, "deps", merged_var_data.deps)
172
+ object.__setattr__(self, "position", merged_var_data.position)
173
+
156
174
  def old_school_imports(self) -> ImportDict:
157
175
  """Return the imports as a mutable dict.
158
176
 
@@ -432,7 +450,7 @@ class Var(Generic[VAR_TYPE]):
432
450
  @dataclasses.dataclass(
433
451
  eq=False,
434
452
  frozen=True,
435
- **{"slots": True} if sys.version_info >= (3, 10) else {},
453
+ slots=True,
436
454
  )
437
455
  class ToVarOperation(ToOperation, cls):
438
456
  """Base class of converting a var to another var type."""
@@ -443,7 +461,12 @@ class Var(Generic[VAR_TYPE]):
443
461
 
444
462
  _default_var_type: ClassVar[GenericType] = default_type
445
463
 
446
- ToVarOperation.__name__ = f'To{cls.__name__.removesuffix("Var")}Operation'
464
+ new_to_var_operation_name = f"To{cls.__name__.removesuffix('Var')}Operation"
465
+ ToVarOperation.__qualname__ = (
466
+ ToVarOperation.__qualname__.removesuffix(ToVarOperation.__name__)
467
+ + new_to_var_operation_name
468
+ )
469
+ ToVarOperation.__name__ = new_to_var_operation_name
447
470
 
448
471
  _var_subclasses.append(VarSubclassEntry(cls, ToVarOperation, python_types))
449
472
 
@@ -492,20 +515,30 @@ class Var(Generic[VAR_TYPE]):
492
515
 
493
516
  @overload
494
517
  def _replace(
495
- self, _var_type: Type[OTHER_VAR_TYPE], merge_var_data=None, **kwargs: Any
518
+ self,
519
+ _var_type: Type[OTHER_VAR_TYPE],
520
+ merge_var_data: VarData | None = None,
521
+ **kwargs: Any,
496
522
  ) -> Var[OTHER_VAR_TYPE]: ...
497
523
 
498
524
  @overload
499
525
  def _replace(
500
- self, _var_type: GenericType | None = None, merge_var_data=None, **kwargs: Any
526
+ self,
527
+ _var_type: GenericType | None = None,
528
+ merge_var_data: VarData | None = None,
529
+ **kwargs: Any,
501
530
  ) -> Self: ...
502
531
 
503
532
  def _replace(
504
- self, _var_type: GenericType | None = None, merge_var_data=None, **kwargs: Any
533
+ self,
534
+ _var_type: GenericType | None = None,
535
+ merge_var_data: VarData | None = None,
536
+ **kwargs: Any,
505
537
  ) -> Self | Var:
506
538
  """Make a copy of this Var with updated fields.
507
539
 
508
540
  Args:
541
+ _var_type: The new type of the Var.
509
542
  merge_var_data: VarData to merge into the existing VarData.
510
543
  **kwargs: Var fields to update.
511
544
 
@@ -539,56 +572,89 @@ class Var(Generic[VAR_TYPE]):
539
572
 
540
573
  return value_with_replaced
541
574
 
575
+ @overload
576
+ @classmethod
577
+ def create( # pyright: ignore[reportOverlappingOverload]
578
+ cls,
579
+ value: bool,
580
+ _var_data: VarData | None = None,
581
+ ) -> BooleanVar: ...
582
+
583
+ @overload
542
584
  @classmethod
543
585
  def create(
544
586
  cls,
545
- value: Any,
546
- _var_is_local: bool | None = None,
547
- _var_is_string: bool | None = None,
587
+ value: int,
548
588
  _var_data: VarData | None = None,
549
- ) -> Var:
589
+ ) -> NumberVar[int]: ...
590
+
591
+ @overload
592
+ @classmethod
593
+ def create(
594
+ cls,
595
+ value: float,
596
+ _var_data: VarData | None = None,
597
+ ) -> NumberVar[float]: ...
598
+
599
+ @overload
600
+ @classmethod
601
+ def create( # pyright: ignore [reportOverlappingOverload]
602
+ cls,
603
+ value: STRING_T,
604
+ _var_data: VarData | None = None,
605
+ ) -> StringVar[STRING_T]: ...
606
+
607
+ @overload
608
+ @classmethod
609
+ def create( # pyright: ignore[reportOverlappingOverload]
610
+ cls,
611
+ value: None,
612
+ _var_data: VarData | None = None,
613
+ ) -> NoneVar: ...
614
+
615
+ @overload
616
+ @classmethod
617
+ def create(
618
+ cls,
619
+ value: MAPPING_TYPE,
620
+ _var_data: VarData | None = None,
621
+ ) -> ObjectVar[MAPPING_TYPE]: ...
622
+
623
+ @overload
624
+ @classmethod
625
+ def create(
626
+ cls,
627
+ value: SEQUENCE_TYPE,
628
+ _var_data: VarData | None = None,
629
+ ) -> ArrayVar[SEQUENCE_TYPE]: ...
630
+
631
+ @overload
632
+ @classmethod
633
+ def create(
634
+ cls,
635
+ value: OTHER_VAR_TYPE,
636
+ _var_data: VarData | None = None,
637
+ ) -> Var[OTHER_VAR_TYPE]: ...
638
+
639
+ @classmethod
640
+ def create(
641
+ cls,
642
+ value: OTHER_VAR_TYPE,
643
+ _var_data: VarData | None = None,
644
+ ) -> Var[OTHER_VAR_TYPE]:
550
645
  """Create a var from a value.
551
646
 
552
647
  Args:
553
648
  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
649
  _var_data: Additional hooks and imports associated with the Var.
557
650
 
558
651
  Returns:
559
652
  The var.
560
653
  """
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
654
  # If the value is already a var, do nothing.
579
655
  if isinstance(value, Var):
580
656
  return value
581
657
 
582
- # Try to pull the imports and hooks from contained values.
583
- if not isinstance(value, str):
584
- return LiteralVar.create(value)
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
658
  return LiteralVar.create(value, _var_data=_var_data)
593
659
 
594
660
  @classmethod
@@ -643,8 +709,8 @@ class Var(Generic[VAR_TYPE]):
643
709
  @overload
644
710
  def to(
645
711
  self,
646
- output: type[dict],
647
- ) -> ObjectVar[dict]: ...
712
+ output: type[MAPPING_TYPE],
713
+ ) -> ObjectVar[MAPPING_TYPE]: ...
648
714
 
649
715
  @overload
650
716
  def to(
@@ -686,14 +752,16 @@ class Var(Generic[VAR_TYPE]):
686
752
 
687
753
  # If the first argument is a python type, we map it to the corresponding Var type.
688
754
  for var_subclass in _var_subclasses[::-1]:
689
- if fixed_output_type in var_subclass.python_types:
755
+ if fixed_output_type in var_subclass.python_types or safe_issubclass(
756
+ fixed_output_type, var_subclass.python_types
757
+ ):
690
758
  return self.to(var_subclass.var_subclass, output)
691
759
 
692
760
  if fixed_output_type is None:
693
- return get_to_operation(NoneVar).create(self) # type: ignore
761
+ return get_to_operation(NoneVar).create(self) # pyright: ignore [reportReturnType]
694
762
 
695
763
  # Handle fixed_output_type being Base or a dataclass.
696
- if can_use_in_object_var(fixed_output_type):
764
+ if can_use_in_object_var(output):
697
765
  return self.to(ObjectVar, output)
698
766
 
699
767
  if inspect.isclass(output):
@@ -707,7 +775,7 @@ class Var(Generic[VAR_TYPE]):
707
775
  to_operation_return = var_subclass.to_var_subclass.create(
708
776
  value=self, _var_type=new_var_type
709
777
  )
710
- return to_operation_return # type: ignore
778
+ return to_operation_return # pyright: ignore [reportReturnType]
711
779
 
712
780
  # If we can't determine the first argument, we just replace the _var_type.
713
781
  if not issubclass(output, Var) or var_type is None:
@@ -725,6 +793,9 @@ class Var(Generic[VAR_TYPE]):
725
793
 
726
794
  return self
727
795
 
796
+ @overload
797
+ def guess_type(self: Var[NoReturn]) -> Var[Any]: ... # pyright: ignore [reportOverlappingOverload]
798
+
728
799
  @overload
729
800
  def guess_type(self: Var[str]) -> StringVar: ...
730
801
 
@@ -734,6 +805,9 @@ class Var(Generic[VAR_TYPE]):
734
805
  @overload
735
806
  def guess_type(self: Var[int] | Var[float] | Var[int | float]) -> NumberVar: ...
736
807
 
808
+ @overload
809
+ def guess_type(self: Var[BASE_TYPE]) -> ObjectVar[BASE_TYPE]: ...
810
+
737
811
  @overload
738
812
  def guess_type(self) -> Self: ...
739
813
 
@@ -820,7 +894,7 @@ class Var(Generic[VAR_TYPE]):
820
894
  return False
821
895
  if issubclass(type_, list):
822
896
  return []
823
- if issubclass(type_, dict):
897
+ if issubclass(type_, Mapping):
824
898
  return {}
825
899
  if issubclass(type_, tuple):
826
900
  return ()
@@ -882,7 +956,7 @@ class Var(Generic[VAR_TYPE]):
882
956
 
883
957
  return setter
884
958
 
885
- def _var_set_state(self, state: type[BaseState] | str):
959
+ def _var_set_state(self, state: type[BaseState] | str) -> Self:
886
960
  """Set the state of the var.
887
961
 
888
962
  Args:
@@ -897,7 +971,7 @@ class Var(Generic[VAR_TYPE]):
897
971
  else format_state_name(state.get_full_name())
898
972
  )
899
973
 
900
- return StateOperation.create(
974
+ return StateOperation.create( # pyright: ignore [reportReturnType]
901
975
  formatted_state_name,
902
976
  self,
903
977
  _var_data=VarData.merge(
@@ -1026,7 +1100,7 @@ class Var(Generic[VAR_TYPE]):
1026
1100
  f"$/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")]
1027
1101
  }
1028
1102
  ),
1029
- ).to(ObjectVar, Dict[str, str])
1103
+ ).to(ObjectVar, Mapping[str, str])
1030
1104
  return refs[LiteralVar.create(str(self))]
1031
1105
 
1032
1106
  @deprecated("Use `.js_type()` instead.")
@@ -1076,43 +1150,6 @@ class Var(Generic[VAR_TYPE]):
1076
1150
  """
1077
1151
  return self
1078
1152
 
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
1153
  def _decode(self) -> Any:
1117
1154
  """Decode Var as a python value.
1118
1155
 
@@ -1123,7 +1160,7 @@ class Var(Generic[VAR_TYPE]):
1123
1160
  The decoded value or the Var name.
1124
1161
  """
1125
1162
  if isinstance(self, LiteralVar):
1126
- return self._var_value # type: ignore
1163
+ return self._var_value
1127
1164
  try:
1128
1165
  return json.loads(str(self))
1129
1166
  except ValueError:
@@ -1174,36 +1211,76 @@ class Var(Generic[VAR_TYPE]):
1174
1211
 
1175
1212
  return ArrayVar.range(first_endpoint, second_endpoint, step)
1176
1213
 
1177
- def __bool__(self) -> bool:
1178
- """Raise exception if using Var in a boolean context.
1214
+ if not TYPE_CHECKING:
1179
1215
 
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
- )
1216
+ def __getattr__(self, name: str):
1217
+ """Get an attribute of the var.
1187
1218
 
1188
- def __iter__(self) -> Any:
1189
- """Raise exception if using Var in an iterable context.
1219
+ Args:
1220
+ name: The name of the attribute.
1190
1221
 
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
- )
1222
+ Raises:
1223
+ VarAttributeError: If the attribute does not exist.
1224
+ UntypedVarError: If the var type is Any.
1225
+ TypeError: If the var type is Any.
1197
1226
 
1198
- def __contains__(self, _: Any) -> Var:
1199
- """Override the 'in' operator to alert the user that it is not supported.
1227
+ # noqa: DAR101 self
1228
+ """
1229
+ if name.startswith("_"):
1230
+ raise VarAttributeError(f"Attribute {name} not found.")
1200
1231
 
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
- )
1232
+ if name == "contains":
1233
+ raise TypeError(
1234
+ f"Var of type {self._var_type} does not support contains check."
1235
+ )
1236
+ if name == "reverse":
1237
+ raise TypeError("Cannot reverse non-list var.")
1238
+
1239
+ if self._var_type is Any:
1240
+ raise exceptions.UntypedVarError(
1241
+ f"You must provide an annotation for the state var `{self!s}`. Annotation cannot be `{self._var_type}`."
1242
+ )
1243
+
1244
+ raise VarAttributeError(
1245
+ f"The State var has no attribute '{name}' or may have been annotated wrongly.",
1246
+ )
1247
+
1248
+ def __bool__(self) -> bool:
1249
+ """Raise exception if using Var in a boolean context.
1250
+
1251
+ Raises:
1252
+ VarTypeError: when attempting to bool-ify the Var.
1253
+
1254
+ # noqa: DAR101 self
1255
+ """
1256
+ raise VarTypeError(
1257
+ f"Cannot convert Var {str(self)!r} to bool for use with `if`, `and`, `or`, and `not`. "
1258
+ "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)."
1259
+ )
1260
+
1261
+ def __iter__(self) -> Any:
1262
+ """Raise exception if using Var in an iterable context.
1263
+
1264
+ Raises:
1265
+ VarTypeError: when attempting to iterate over the Var.
1266
+
1267
+ # noqa: DAR101 self
1268
+ """
1269
+ raise VarTypeError(
1270
+ f"Cannot iterate over Var {str(self)!r}. Instead use `rx.foreach`."
1271
+ )
1272
+
1273
+ def __contains__(self, _: Any) -> Var:
1274
+ """Override the 'in' operator to alert the user that it is not supported.
1275
+
1276
+ Raises:
1277
+ VarTypeError: the operation is not supported
1278
+
1279
+ # noqa: DAR101 self
1280
+ """
1281
+ raise VarTypeError(
1282
+ "'in' operator not supported for Var types, use Var.contains() instead."
1283
+ )
1207
1284
 
1208
1285
 
1209
1286
  OUTPUT = TypeVar("OUTPUT", bound=Var)
@@ -1250,7 +1327,7 @@ class ToOperation:
1250
1327
  """
1251
1328
  return VarData.merge(
1252
1329
  self._original._get_all_var_data(),
1253
- self._var_data, # type: ignore
1330
+ self._var_data,
1254
1331
  )
1255
1332
 
1256
1333
  @classmethod
@@ -1271,10 +1348,10 @@ class ToOperation:
1271
1348
  The ToOperation.
1272
1349
  """
1273
1350
  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
1351
+ _js_expr="", # pyright: ignore [reportCallIssue]
1352
+ _var_data=_var_data, # pyright: ignore [reportCallIssue]
1353
+ _var_type=_var_type or cls._default_var_type, # pyright: ignore [reportCallIssue, reportAttributeAccessIssue]
1354
+ _original=value, # pyright: ignore [reportCallIssue]
1278
1355
  )
1279
1356
 
1280
1357
 
@@ -1336,7 +1413,7 @@ class LiteralVar(Var):
1336
1413
  _var_literal_subclasses.append((cls, var_subclass))
1337
1414
 
1338
1415
  @classmethod
1339
- def create(
1416
+ def create( # pyright: ignore [reportArgumentType]
1340
1417
  cls,
1341
1418
  value: Any,
1342
1419
  _var_data: VarData | None = None,
@@ -1354,7 +1431,7 @@ class LiteralVar(Var):
1354
1431
  TypeError: If the value is not a supported type for LiteralVar.
1355
1432
  """
1356
1433
  from .object import LiteralObjectVar
1357
- from .sequence import LiteralStringVar
1434
+ from .sequence import ArrayVar, LiteralStringVar
1358
1435
 
1359
1436
  if isinstance(value, Var):
1360
1437
  if _var_data is None:
@@ -1373,7 +1450,7 @@ class LiteralVar(Var):
1373
1450
 
1374
1451
  serialized_value = serializers.serialize(value)
1375
1452
  if serialized_value is not None:
1376
- if isinstance(serialized_value, dict):
1453
+ if isinstance(serialized_value, Mapping):
1377
1454
  return LiteralObjectVar.create(
1378
1455
  serialized_value,
1379
1456
  _var_type=type(value),
@@ -1410,6 +1487,9 @@ class LiteralVar(Var):
1410
1487
  _var_data=_var_data,
1411
1488
  )
1412
1489
 
1490
+ if isinstance(value, range):
1491
+ return ArrayVar.range(value.start, value.stop, value.step)
1492
+
1413
1493
  raise TypeError(
1414
1494
  f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}."
1415
1495
  )
@@ -1417,6 +1497,12 @@ class LiteralVar(Var):
1417
1497
  def __post_init__(self):
1418
1498
  """Post-initialize the var."""
1419
1499
 
1500
+ @property
1501
+ def _var_value(self) -> Any:
1502
+ raise NotImplementedError(
1503
+ "LiteralVar subclasses must implement the _var_value property."
1504
+ )
1505
+
1420
1506
  def json(self) -> str:
1421
1507
  """Serialize the var to a JSON string.
1422
1508
 
@@ -1463,7 +1549,7 @@ T = TypeVar("T")
1463
1549
 
1464
1550
  # NoReturn is used to match CustomVarOperationReturn with no type hint.
1465
1551
  @overload
1466
- def var_operation(
1552
+ def var_operation( # pyright: ignore [reportOverlappingOverload]
1467
1553
  func: Callable[P, CustomVarOperationReturn[NoReturn]],
1468
1554
  ) -> Callable[P, Var]: ...
1469
1555
 
@@ -1489,7 +1575,7 @@ def var_operation(
1489
1575
  ) -> Callable[P, StringVar]: ...
1490
1576
 
1491
1577
 
1492
- LIST_T = TypeVar("LIST_T", bound=Union[List[Any], Tuple, Set])
1578
+ LIST_T = TypeVar("LIST_T", bound=Sequence)
1493
1579
 
1494
1580
 
1495
1581
  @overload
@@ -1498,7 +1584,7 @@ def var_operation(
1498
1584
  ) -> Callable[P, ArrayVar[LIST_T]]: ...
1499
1585
 
1500
1586
 
1501
- OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Dict)
1587
+ OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Mapping)
1502
1588
 
1503
1589
 
1504
1590
  @overload
@@ -1513,7 +1599,7 @@ def var_operation(
1513
1599
  ) -> Callable[P, Var[T]]: ...
1514
1600
 
1515
1601
 
1516
- def var_operation(
1602
+ def var_operation( # pyright: ignore [reportInconsistentOverload]
1517
1603
  func: Callable[P, CustomVarOperationReturn[T]],
1518
1604
  ) -> Callable[P, Var[T]]:
1519
1605
  """Decorator for creating a var operation.
@@ -1547,7 +1633,7 @@ def var_operation(
1547
1633
  return CustomVarOperation.create(
1548
1634
  name=func.__name__,
1549
1635
  args=tuple(list(args_vars.items()) + list(kwargs_vars.items())),
1550
- return_var=func(*args_vars.values(), **kwargs_vars), # type: ignore
1636
+ return_var=func(*args_vars.values(), **kwargs_vars), # pyright: ignore [reportCallIssue, reportReturnType]
1551
1637
  ).guess_type()
1552
1638
 
1553
1639
  return wrapper
@@ -1573,25 +1659,100 @@ def figure_out_type(value: Any) -> types.GenericType:
1573
1659
  return Set[unionize(*(figure_out_type(v) for v in value))]
1574
1660
  if isinstance(value, tuple):
1575
1661
  return Tuple[unionize(*(figure_out_type(v) for v in value)), ...]
1576
- if isinstance(value, dict):
1577
- return Dict[
1662
+ if isinstance(value, Mapping):
1663
+ return Mapping[
1578
1664
  unionize(*(figure_out_type(k) for k in value)),
1579
1665
  unionize(*(figure_out_type(v) for v in value.values())),
1580
1666
  ]
1581
1667
  return type(value)
1582
1668
 
1583
1669
 
1584
- class cached_property_no_lock(functools.cached_property):
1585
- """A special version of functools.cached_property that does not use a lock."""
1670
+ GLOBAL_CACHE = {}
1671
+
1672
+
1673
+ class cached_property: # noqa: N801
1674
+ """A cached property that caches the result of the function."""
1586
1675
 
1587
- def __init__(self, func):
1588
- """Initialize the cached_property_no_lock.
1676
+ def __init__(self, func: Callable):
1677
+ """Initialize the cached_property.
1589
1678
 
1590
1679
  Args:
1591
1680
  func: The function to cache.
1592
1681
  """
1593
- super().__init__(func)
1594
- self.lock = contextlib.nullcontext()
1682
+ self._func = func
1683
+ self._attrname = None
1684
+
1685
+ def __set_name__(self, owner: Any, name: str):
1686
+ """Set the name of the cached property.
1687
+
1688
+ Args:
1689
+ owner: The owner of the cached property.
1690
+ name: The name of the cached property.
1691
+
1692
+ Raises:
1693
+ TypeError: If the cached property is assigned to two different names.
1694
+ """
1695
+ if self._attrname is None:
1696
+ self._attrname = name
1697
+
1698
+ original_del = getattr(owner, "__del__", None)
1699
+
1700
+ def delete_property(this: Any):
1701
+ """Delete the cached property.
1702
+
1703
+ Args:
1704
+ this: The object to delete the cached property from.
1705
+ """
1706
+ cached_field_name = "_reflex_cache_" + name
1707
+ try:
1708
+ unique_id = object.__getattribute__(this, cached_field_name)
1709
+ except AttributeError:
1710
+ if original_del is not None:
1711
+ original_del(this)
1712
+ return
1713
+ if unique_id in GLOBAL_CACHE:
1714
+ del GLOBAL_CACHE[unique_id]
1715
+
1716
+ if original_del is not None:
1717
+ original_del(this)
1718
+
1719
+ owner.__del__ = delete_property
1720
+
1721
+ elif name != self._attrname:
1722
+ raise TypeError(
1723
+ "Cannot assign the same cached_property to two different names "
1724
+ f"({self._attrname!r} and {name!r})."
1725
+ )
1726
+
1727
+ def __get__(self, instance: Any, owner: Type | None = None):
1728
+ """Get the cached property.
1729
+
1730
+ Args:
1731
+ instance: The instance to get the cached property from.
1732
+ owner: The owner of the cached property.
1733
+
1734
+ Returns:
1735
+ The cached property.
1736
+
1737
+ Raises:
1738
+ TypeError: If the class does not have __set_name__.
1739
+ """
1740
+ if self._attrname is None:
1741
+ raise TypeError(
1742
+ "Cannot use cached_property on a class without __set_name__."
1743
+ )
1744
+ cached_field_name = "_reflex_cache_" + self._attrname
1745
+ try:
1746
+ unique_id = object.__getattribute__(instance, cached_field_name)
1747
+ except AttributeError:
1748
+ unique_id = uuid.uuid4().int
1749
+ object.__setattr__(instance, cached_field_name, unique_id)
1750
+ if unique_id not in GLOBAL_CACHE:
1751
+ GLOBAL_CACHE[unique_id] = self._func(instance)
1752
+ return GLOBAL_CACHE[unique_id]
1753
+
1754
+
1755
+ cached_property_no_lock = cached_property
1595
1756
 
1596
1757
 
1597
1758
  class CachedVarOperation:
@@ -1617,7 +1778,7 @@ class CachedVarOperation:
1617
1778
 
1618
1779
  next_class = parent_classes[parent_classes.index(CachedVarOperation) + 1]
1619
1780
 
1620
- return next_class.__getattr__(self, name) # type: ignore
1781
+ return next_class.__getattr__(self, name)
1621
1782
 
1622
1783
  def _get_all_var_data(self) -> VarData | None:
1623
1784
  """Get all VarData associated with the Var.
@@ -1639,7 +1800,7 @@ class CachedVarOperation:
1639
1800
  value._get_all_var_data() if isinstance(value, Var) else None
1640
1801
  for value in (
1641
1802
  getattr(self, field.name)
1642
- for field in dataclasses.fields(self) # type: ignore
1803
+ for field in dataclasses.fields(self) # pyright: ignore [reportArgumentType]
1643
1804
  )
1644
1805
  ),
1645
1806
  self._var_data,
@@ -1656,7 +1817,7 @@ class CachedVarOperation:
1656
1817
  type(self).__name__,
1657
1818
  *[
1658
1819
  getattr(self, field.name)
1659
- for field in dataclasses.fields(self) # type: ignore
1820
+ for field in dataclasses.fields(self) # pyright: ignore [reportArgumentType]
1660
1821
  if field.name not in ["_js_expr", "_var_data", "_var_type"]
1661
1822
  ],
1662
1823
  )
@@ -1673,7 +1834,7 @@ def and_operation(a: Var | Any, b: Var | Any) -> Var:
1673
1834
  Returns:
1674
1835
  The result of the logical AND operation.
1675
1836
  """
1676
- return _and_operation(a, b) # type: ignore
1837
+ return _and_operation(a, b)
1677
1838
 
1678
1839
 
1679
1840
  @var_operation
@@ -1703,7 +1864,7 @@ def or_operation(a: Var | Any, b: Var | Any) -> Var:
1703
1864
  Returns:
1704
1865
  The result of the logical OR operation.
1705
1866
  """
1706
- return _or_operation(a, b) # type: ignore
1867
+ return _or_operation(a, b)
1707
1868
 
1708
1869
 
1709
1870
  @var_operation
@@ -1726,7 +1887,7 @@ def _or_operation(a: Var, b: Var):
1726
1887
  @dataclasses.dataclass(
1727
1888
  eq=False,
1728
1889
  frozen=True,
1729
- **{"slots": True} if sys.version_info >= (3, 10) else {},
1890
+ slots=True,
1730
1891
  )
1731
1892
  class CallableVar(Var):
1732
1893
  """Decorate a Var-returning function to act as both a Var and a function.
@@ -1757,7 +1918,7 @@ class CallableVar(Var):
1757
1918
  object.__setattr__(self, "fn", fn)
1758
1919
  object.__setattr__(self, "original_var", original_var)
1759
1920
 
1760
- def __call__(self, *args, **kwargs) -> Var:
1921
+ def __call__(self, *args: Any, **kwargs: Any) -> Var:
1761
1922
  """Call the decorated function.
1762
1923
 
1763
1924
  Args:
@@ -1807,7 +1968,7 @@ def is_computed_var(obj: Any) -> TypeGuard[ComputedVar]:
1807
1968
  @dataclasses.dataclass(
1808
1969
  eq=False,
1809
1970
  frozen=True,
1810
- **{"slots": True} if sys.version_info >= (3, 10) else {},
1971
+ slots=True,
1811
1972
  )
1812
1973
  class ComputedVar(Var[RETURN_TYPE]):
1813
1974
  """A field with computed getters."""
@@ -1822,7 +1983,7 @@ class ComputedVar(Var[RETURN_TYPE]):
1822
1983
  _initial_value: RETURN_TYPE | types.Unset = dataclasses.field(default=types.Unset())
1823
1984
 
1824
1985
  # Explicit var dependencies to track
1825
- _static_deps: set[str] = dataclasses.field(default_factory=set)
1986
+ _static_deps: dict[str | None, set[str]] = dataclasses.field(default_factory=dict)
1826
1987
 
1827
1988
  # Whether var dependencies should be auto-determined
1828
1989
  _auto_deps: bool = dataclasses.field(default=True)
@@ -1832,13 +1993,13 @@ class ComputedVar(Var[RETURN_TYPE]):
1832
1993
 
1833
1994
  _fget: Callable[[BaseState], RETURN_TYPE] = dataclasses.field(
1834
1995
  default_factory=lambda: lambda _: None
1835
- ) # type: ignore
1996
+ ) # pyright: ignore [reportAssignmentType]
1836
1997
 
1837
1998
  def __init__(
1838
1999
  self,
1839
2000
  fget: Callable[[BASE_STATE], RETURN_TYPE],
1840
2001
  initial_value: RETURN_TYPE | types.Unset = types.Unset(),
1841
- cache: bool = False,
2002
+ cache: bool = True,
1842
2003
  deps: Optional[List[Union[str, Var]]] = None,
1843
2004
  auto_deps: bool = True,
1844
2005
  interval: Optional[Union[int, datetime.timedelta]] = None,
@@ -1859,19 +2020,14 @@ class ComputedVar(Var[RETURN_TYPE]):
1859
2020
 
1860
2021
  Raises:
1861
2022
  TypeError: If the computed var dependencies are not Var instances or var names.
2023
+ UntypedComputedVarError: If the computed var is untyped.
1862
2024
  """
1863
2025
  hint = kwargs.pop("return_type", None) or get_type_hints(fget).get(
1864
2026
  "return", Any
1865
2027
  )
1866
2028
 
1867
2029
  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
-
2030
+ raise UntypedComputedVarError(var_name=fget.__name__)
1875
2031
  kwargs.setdefault("_js_expr", fget.__name__)
1876
2032
  kwargs.setdefault("_var_type", hint)
1877
2033
 
@@ -1897,28 +2053,78 @@ class ComputedVar(Var[RETURN_TYPE]):
1897
2053
 
1898
2054
  object.__setattr__(self, "_update_interval", interval)
1899
2055
 
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
2056
  object.__setattr__(
1912
2057
  self,
1913
2058
  "_static_deps",
1914
- {dep._js_expr if isinstance(dep, Var) else dep for dep in deps},
2059
+ self._calculate_static_deps(deps),
1915
2060
  )
1916
2061
  object.__setattr__(self, "_auto_deps", auto_deps)
1917
2062
 
1918
2063
  object.__setattr__(self, "_fget", fget)
1919
2064
 
2065
+ def _calculate_static_deps(
2066
+ self,
2067
+ deps: Union[List[Union[str, Var]], dict[str | None, set[str]]] | None = None,
2068
+ ) -> dict[str | None, set[str]]:
2069
+ """Calculate the static dependencies of the computed var from user input or existing dependencies.
2070
+
2071
+ Args:
2072
+ deps: The user input dependencies or existing dependencies.
2073
+
2074
+ Returns:
2075
+ The static dependencies.
2076
+ """
2077
+ if isinstance(deps, dict):
2078
+ # Assume a dict is coming from _replace, so no special processing.
2079
+ return deps
2080
+ _static_deps = {}
2081
+ if deps is not None:
2082
+ for dep in deps:
2083
+ _static_deps = self._add_static_dep(dep, _static_deps)
2084
+ return _static_deps
2085
+
2086
+ def _add_static_dep(
2087
+ self, dep: Union[str, Var], deps: dict[str | None, set[str]] | None = None
2088
+ ) -> dict[str | None, set[str]]:
2089
+ """Add a static dependency to the computed var or existing dependency set.
2090
+
2091
+ Args:
2092
+ dep: The dependency to add.
2093
+ deps: The existing dependency set.
2094
+
2095
+ Returns:
2096
+ The updated dependency set.
2097
+
2098
+ Raises:
2099
+ TypeError: If the computed var dependencies are not Var instances or var names.
2100
+ """
2101
+ if deps is None:
2102
+ deps = self._static_deps
2103
+ if isinstance(dep, Var):
2104
+ state_name = (
2105
+ all_var_data.state
2106
+ if (all_var_data := dep._get_all_var_data()) and all_var_data.state
2107
+ else None
2108
+ )
2109
+ if all_var_data is not None:
2110
+ var_name = all_var_data.field_name
2111
+ else:
2112
+ var_name = dep._js_expr
2113
+ deps.setdefault(state_name, set()).add(var_name)
2114
+ elif isinstance(dep, str) and dep != "":
2115
+ deps.setdefault(None, set()).add(dep)
2116
+ else:
2117
+ raise TypeError(
2118
+ "ComputedVar dependencies must be Var instances or var names (non-empty strings)."
2119
+ )
2120
+ return deps
2121
+
1920
2122
  @override
1921
- def _replace(self, merge_var_data=None, **kwargs: Any) -> Self:
2123
+ def _replace(
2124
+ self,
2125
+ merge_var_data: VarData | None = None,
2126
+ **kwargs: Any,
2127
+ ) -> Self:
1922
2128
  """Replace the attributes of the ComputedVar.
1923
2129
 
1924
2130
  Args:
@@ -1931,6 +2137,8 @@ class ComputedVar(Var[RETURN_TYPE]):
1931
2137
  Raises:
1932
2138
  TypeError: If kwargs contains keys that are not allowed.
1933
2139
  """
2140
+ if "deps" in kwargs:
2141
+ kwargs["deps"] = self._calculate_static_deps(kwargs["deps"])
1934
2142
  field_values = {
1935
2143
  "fget": kwargs.pop("fget", self._fget),
1936
2144
  "initial_value": kwargs.pop("initial_value", self._initial_value),
@@ -1944,6 +2152,7 @@ class ComputedVar(Var[RETURN_TYPE]):
1944
2152
  "_var_data": kwargs.pop(
1945
2153
  "_var_data", VarData.merge(self._var_data, merge_var_data)
1946
2154
  ),
2155
+ "return_type": kwargs.pop("return_type", self._var_type),
1947
2156
  }
1948
2157
 
1949
2158
  if kwargs:
@@ -1986,6 +2195,13 @@ class ComputedVar(Var[RETURN_TYPE]):
1986
2195
  return True
1987
2196
  return datetime.datetime.now() - last_updated > self._update_interval
1988
2197
 
2198
+ @overload
2199
+ def __get__(
2200
+ self: ComputedVar[bool],
2201
+ instance: None,
2202
+ owner: Type,
2203
+ ) -> BooleanVar: ...
2204
+
1989
2205
  @overload
1990
2206
  def __get__(
1991
2207
  self: ComputedVar[int] | ComputedVar[float],
@@ -2002,10 +2218,10 @@ class ComputedVar(Var[RETURN_TYPE]):
2002
2218
 
2003
2219
  @overload
2004
2220
  def __get__(
2005
- self: ComputedVar[dict[DICT_KEY, DICT_VAL]],
2221
+ self: ComputedVar[Mapping[DICT_KEY, DICT_VAL]],
2006
2222
  instance: None,
2007
2223
  owner: Type,
2008
- ) -> ObjectVar[dict[DICT_KEY, DICT_VAL]]: ...
2224
+ ) -> ObjectVar[Mapping[DICT_KEY, DICT_VAL]]: ...
2009
2225
 
2010
2226
  @overload
2011
2227
  def __get__(
@@ -2014,13 +2230,6 @@ class ComputedVar(Var[RETURN_TYPE]):
2014
2230
  owner: Type,
2015
2231
  ) -> ArrayVar[list[LIST_INSIDE]]: ...
2016
2232
 
2017
- @overload
2018
- def __get__(
2019
- self: ComputedVar[set[LIST_INSIDE]],
2020
- instance: None,
2021
- owner: Type,
2022
- ) -> ArrayVar[set[LIST_INSIDE]]: ...
2023
-
2024
2233
  @overload
2025
2234
  def __get__(
2026
2235
  self: ComputedVar[tuple[LIST_INSIDE, ...]],
@@ -2034,7 +2243,7 @@ class ComputedVar(Var[RETURN_TYPE]):
2034
2243
  @overload
2035
2244
  def __get__(self, instance: BaseState, owner: Type) -> RETURN_TYPE: ...
2036
2245
 
2037
- def __get__(self, instance: BaseState | None, owner):
2246
+ def __get__(self, instance: BaseState | None, owner: Type):
2038
2247
  """Get the ComputedVar value.
2039
2248
 
2040
2249
  If the value is already cached on the instance, return the cached value.
@@ -2077,130 +2286,69 @@ class ComputedVar(Var[RETURN_TYPE]):
2077
2286
  setattr(instance, self._last_updated_attr, datetime.datetime.now())
2078
2287
  value = getattr(instance, self._cache_attr)
2079
2288
 
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
- )
2289
+ self._check_deprecated_return_type(instance, value)
2088
2290
 
2089
2291
  return value
2090
2292
 
2293
+ def _check_deprecated_return_type(self, instance: BaseState, value: Any) -> None:
2294
+ if not _isinstance(value, self._var_type):
2295
+ console.error(
2296
+ f"Computed var '{type(instance).__name__}.{self._js_expr}' must return"
2297
+ f" type '{self._var_type}', got '{type(value)}'."
2298
+ )
2299
+
2091
2300
  def _deps(
2092
2301
  self,
2093
- objclass: Type,
2302
+ objclass: Type[BaseState],
2094
2303
  obj: FunctionType | CodeType | None = None,
2095
- self_name: Optional[str] = None,
2096
- ) -> set[str]:
2304
+ ) -> dict[str, set[str]]:
2097
2305
  """Determine var dependencies of this ComputedVar.
2098
2306
 
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".
2307
+ Save references to attributes accessed on "self" or other fetched states.
2308
+
2309
+ Recursively called when the function makes a method call on "self" or
2310
+ define comprehensions or nested functions that may reference "self".
2102
2311
 
2103
2312
  Args:
2104
2313
  objclass: the class obj this ComputedVar is attached to.
2105
2314
  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
2315
 
2108
2316
  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).
2317
+ A dictionary mapping state names to the set of variable names
2318
+ accessed by the given obj.
2114
2319
  """
2320
+ from .dep_tracking import DependencyTracker
2321
+
2322
+ d = {}
2323
+ if self._static_deps:
2324
+ d.update(self._static_deps)
2325
+ # None is a placeholder for the current state class.
2326
+ if None in d:
2327
+ d[objclass.get_full_name()] = d.pop(None)
2328
+
2115
2329
  if not self._auto_deps:
2116
- return self._static_deps
2117
- d = self._static_deps.copy()
2330
+ return d
2331
+
2118
2332
  if obj is None:
2119
2333
  fget = self._fget
2120
2334
  if fget is not None:
2121
2335
  obj = cast(FunctionType, fget)
2122
2336
  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
2337
+ return d
2130
2338
 
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
2339
+ try:
2340
+ return DependencyTracker(
2341
+ func=obj, state_cls=objclass, dependencies=d
2342
+ ).dependencies
2343
+ except Exception as e:
2344
+ console.warn(
2345
+ "Failed to automatically determine dependencies for computed var "
2346
+ f"{objclass.__name__}.{self._js_expr}: {e}. "
2347
+ "Provide static_deps and set auto_deps=False to suppress this warning."
2348
+ )
2349
+ return d
2202
2350
 
2203
- def mark_dirty(self, instance) -> None:
2351
+ def mark_dirty(self, instance: BaseState) -> None:
2204
2352
  """Mark this ComputedVar as dirty.
2205
2353
 
2206
2354
  Args:
@@ -2209,6 +2357,37 @@ class ComputedVar(Var[RETURN_TYPE]):
2209
2357
  with contextlib.suppress(AttributeError):
2210
2358
  delattr(instance, self._cache_attr)
2211
2359
 
2360
+ def add_dependency(self, objclass: Type[BaseState], dep: Var):
2361
+ """Explicitly add a dependency to the ComputedVar.
2362
+
2363
+ After adding the dependency, when the `dep` changes, this computed var
2364
+ will be marked dirty.
2365
+
2366
+ Args:
2367
+ objclass: The class obj this ComputedVar is attached to.
2368
+ dep: The dependency to add.
2369
+
2370
+ Raises:
2371
+ VarDependencyError: If the dependency is not a Var instance with a
2372
+ state and field name
2373
+ """
2374
+ if all_var_data := dep._get_all_var_data():
2375
+ state_name = all_var_data.state
2376
+ if state_name:
2377
+ var_name = all_var_data.field_name
2378
+ if var_name:
2379
+ self._static_deps.setdefault(state_name, set()).add(var_name)
2380
+ objclass.get_root_state().get_class_substate(
2381
+ state_name
2382
+ )._var_dependencies.setdefault(var_name, set()).add(
2383
+ (objclass.get_full_name(), self._js_expr)
2384
+ )
2385
+ return
2386
+ raise VarDependencyError(
2387
+ "ComputedVar dependencies must be Var instances with a state and "
2388
+ f"field name, got {dep!r}."
2389
+ )
2390
+
2212
2391
  def _determine_var_type(self) -> Type:
2213
2392
  """Get the type of the var.
2214
2393
 
@@ -2218,7 +2397,7 @@ class ComputedVar(Var[RETURN_TYPE]):
2218
2397
  hints = get_type_hints(self._fget)
2219
2398
  if "return" in hints:
2220
2399
  return hints["return"]
2221
- return Any
2400
+ return Any # pyright: ignore [reportReturnType]
2222
2401
 
2223
2402
  @property
2224
2403
  def __class__(self) -> Type:
@@ -2245,6 +2424,126 @@ class DynamicRouteVar(ComputedVar[Union[str, List[str]]]):
2245
2424
  pass
2246
2425
 
2247
2426
 
2427
+ async def _default_async_computed_var(_self: BaseState) -> Any:
2428
+ return None
2429
+
2430
+
2431
+ @dataclasses.dataclass(
2432
+ eq=False,
2433
+ frozen=True,
2434
+ init=False,
2435
+ slots=True,
2436
+ )
2437
+ class AsyncComputedVar(ComputedVar[RETURN_TYPE]):
2438
+ """A computed var that wraps a coroutinefunction."""
2439
+
2440
+ _fget: Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]] = (
2441
+ dataclasses.field(default=_default_async_computed_var)
2442
+ )
2443
+
2444
+ @overload
2445
+ def __get__(
2446
+ self: AsyncComputedVar[bool],
2447
+ instance: None,
2448
+ owner: Type,
2449
+ ) -> BooleanVar: ...
2450
+
2451
+ @overload
2452
+ def __get__(
2453
+ self: AsyncComputedVar[int] | ComputedVar[float],
2454
+ instance: None,
2455
+ owner: Type,
2456
+ ) -> NumberVar: ...
2457
+
2458
+ @overload
2459
+ def __get__(
2460
+ self: AsyncComputedVar[str],
2461
+ instance: None,
2462
+ owner: Type,
2463
+ ) -> StringVar: ...
2464
+
2465
+ @overload
2466
+ def __get__(
2467
+ self: AsyncComputedVar[Mapping[DICT_KEY, DICT_VAL]],
2468
+ instance: None,
2469
+ owner: Type,
2470
+ ) -> ObjectVar[Mapping[DICT_KEY, DICT_VAL]]: ...
2471
+
2472
+ @overload
2473
+ def __get__(
2474
+ self: AsyncComputedVar[list[LIST_INSIDE]],
2475
+ instance: None,
2476
+ owner: Type,
2477
+ ) -> ArrayVar[list[LIST_INSIDE]]: ...
2478
+
2479
+ @overload
2480
+ def __get__(
2481
+ self: AsyncComputedVar[tuple[LIST_INSIDE, ...]],
2482
+ instance: None,
2483
+ owner: Type,
2484
+ ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ...
2485
+
2486
+ @overload
2487
+ def __get__(self, instance: None, owner: Type) -> AsyncComputedVar[RETURN_TYPE]: ...
2488
+
2489
+ @overload
2490
+ def __get__(
2491
+ self, instance: BaseState, owner: Type
2492
+ ) -> Coroutine[None, None, RETURN_TYPE]: ...
2493
+
2494
+ def __get__(
2495
+ self, instance: BaseState | None, owner
2496
+ ) -> Var | Coroutine[None, None, RETURN_TYPE]:
2497
+ """Get the ComputedVar value.
2498
+
2499
+ If the value is already cached on the instance, return the cached value.
2500
+
2501
+ Args:
2502
+ instance: the instance of the class accessing this computed var.
2503
+ owner: the class that this descriptor is attached to.
2504
+
2505
+ Returns:
2506
+ The value of the var for the given instance.
2507
+ """
2508
+ if instance is None:
2509
+ return super(AsyncComputedVar, self).__get__(instance, owner)
2510
+
2511
+ if not self._cache:
2512
+
2513
+ async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE:
2514
+ value = await self.fget(instance)
2515
+ self._check_deprecated_return_type(instance, value)
2516
+ return value
2517
+
2518
+ return _awaitable_result()
2519
+ else:
2520
+ # handle caching
2521
+ async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE:
2522
+ if not hasattr(instance, self._cache_attr) or self.needs_update(
2523
+ instance
2524
+ ):
2525
+ # Set cache attr on state instance.
2526
+ setattr(instance, self._cache_attr, await self.fget(instance))
2527
+ # Ensure the computed var gets serialized to redis.
2528
+ instance._was_touched = True
2529
+ # Set the last updated timestamp on the state instance.
2530
+ setattr(instance, self._last_updated_attr, datetime.datetime.now())
2531
+ value = getattr(instance, self._cache_attr)
2532
+ self._check_deprecated_return_type(instance, value)
2533
+ return value
2534
+
2535
+ return _awaitable_result()
2536
+
2537
+ @property
2538
+ def fget(self) -> Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]]:
2539
+ """Get the getter function.
2540
+
2541
+ Returns:
2542
+ The getter function.
2543
+ """
2544
+ return self._fget
2545
+
2546
+
2248
2547
  if TYPE_CHECKING:
2249
2548
  BASE_STATE = TypeVar("BASE_STATE", bound=BaseState)
2250
2549
 
@@ -2253,20 +2552,20 @@ if TYPE_CHECKING:
2253
2552
  def computed_var(
2254
2553
  fget: None = None,
2255
2554
  initial_value: Any | types.Unset = types.Unset(),
2256
- cache: bool = False,
2555
+ cache: bool = True,
2257
2556
  deps: Optional[List[Union[str, Var]]] = None,
2258
2557
  auto_deps: bool = True,
2259
2558
  interval: Optional[Union[datetime.timedelta, int]] = None,
2260
2559
  backend: bool | None = None,
2261
2560
  **kwargs,
2262
- ) -> Callable[[Callable[[BASE_STATE], RETURN_TYPE]], ComputedVar[RETURN_TYPE]]: ...
2561
+ ) -> Callable[[Callable[[BASE_STATE], RETURN_TYPE]], ComputedVar[RETURN_TYPE]]: ... # pyright: ignore [reportInvalidTypeVarUse]
2263
2562
 
2264
2563
 
2265
2564
  @overload
2266
2565
  def computed_var(
2267
2566
  fget: Callable[[BASE_STATE], RETURN_TYPE],
2268
2567
  initial_value: RETURN_TYPE | types.Unset = types.Unset(),
2269
- cache: bool = False,
2568
+ cache: bool = True,
2270
2569
  deps: Optional[List[Union[str, Var]]] = None,
2271
2570
  auto_deps: bool = True,
2272
2571
  interval: Optional[Union[datetime.timedelta, int]] = None,
@@ -2278,7 +2577,7 @@ def computed_var(
2278
2577
  def computed_var(
2279
2578
  fget: Callable[[BASE_STATE], Any] | None = None,
2280
2579
  initial_value: Any | types.Unset = types.Unset(),
2281
- cache: Optional[bool] = None,
2580
+ cache: bool = True,
2282
2581
  deps: Optional[List[Union[str, Var]]] = None,
2283
2582
  auto_deps: bool = True,
2284
2583
  interval: Optional[Union[datetime.timedelta, int]] = None,
@@ -2304,15 +2603,6 @@ def computed_var(
2304
2603
  ValueError: If caching is disabled and an update interval is set.
2305
2604
  VarDependencyError: If user supplies dependencies without caching.
2306
2605
  """
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
2606
  if cache is False and interval is not None:
2317
2607
  raise ValueError("Cannot set update interval without caching.")
2318
2608
 
@@ -2320,10 +2610,27 @@ def computed_var(
2320
2610
  raise VarDependencyError("Cannot track dependencies without caching.")
2321
2611
 
2322
2612
  if fget is not None:
2323
- return ComputedVar(fget, cache=cache)
2613
+ if inspect.iscoroutinefunction(fget):
2614
+ computed_var_cls = AsyncComputedVar
2615
+ else:
2616
+ computed_var_cls = ComputedVar
2617
+ return computed_var_cls(
2618
+ fget,
2619
+ initial_value=initial_value,
2620
+ cache=cache,
2621
+ deps=deps,
2622
+ auto_deps=auto_deps,
2623
+ interval=interval,
2624
+ backend=backend,
2625
+ **kwargs,
2626
+ )
2324
2627
 
2325
2628
  def wrapper(fget: Callable[[BASE_STATE], Any]) -> ComputedVar:
2326
- return ComputedVar(
2629
+ if inspect.iscoroutinefunction(fget):
2630
+ computed_var_cls = AsyncComputedVar
2631
+ else:
2632
+ computed_var_cls = ComputedVar
2633
+ return computed_var_cls(
2327
2634
  fget,
2328
2635
  initial_value=initial_value,
2329
2636
  cache=cache,
@@ -2392,7 +2699,7 @@ def var_operation_return(
2392
2699
  @dataclasses.dataclass(
2393
2700
  eq=False,
2394
2701
  frozen=True,
2395
- **{"slots": True} if sys.version_info >= (3, 10) else {},
2702
+ slots=True,
2396
2703
  )
2397
2704
  class CustomVarOperation(CachedVarOperation, Var[T]):
2398
2705
  """Base class for custom var operations."""
@@ -2463,7 +2770,7 @@ class NoneVar(Var[None], python_types=type(None)):
2463
2770
  @dataclasses.dataclass(
2464
2771
  eq=False,
2465
2772
  frozen=True,
2466
- **{"slots": True} if sys.version_info >= (3, 10) else {},
2773
+ slots=True,
2467
2774
  )
2468
2775
  class LiteralNoneVar(LiteralVar, NoneVar):
2469
2776
  """A var representing None."""
@@ -2525,7 +2832,7 @@ def get_to_operation(var_subclass: Type[Var]) -> Type[ToOperation]:
2525
2832
  @dataclasses.dataclass(
2526
2833
  eq=False,
2527
2834
  frozen=True,
2528
- **{"slots": True} if sys.version_info >= (3, 10) else {},
2835
+ slots=True,
2529
2836
  )
2530
2837
  class StateOperation(CachedVarOperation, Var):
2531
2838
  """A var operation that accesses a field on an object."""
@@ -2597,7 +2904,7 @@ def get_uuid_string_var() -> Var:
2597
2904
  unique_uuid_var = get_unique_variable_name()
2598
2905
  unique_uuid_var_data = VarData(
2599
2906
  imports={
2600
- f"$/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")}, # type: ignore
2907
+ f"$/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")}, # pyright: ignore [reportArgumentType]
2601
2908
  "react": "useMemo",
2602
2909
  },
2603
2910
  hooks={f"const {unique_uuid_var} = useMemo(generateUUID, [])": None},
@@ -2657,7 +2964,7 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]:
2657
2964
  elif not isinstance(sub, str):
2658
2965
  # Recurse into dict values.
2659
2966
  if hasattr(sub, "values") and callable(sub.values):
2660
- var_datas.extend(_extract_var_data(sub.values()))
2967
+ var_datas.extend(_extract_var_data(sub.values())) # pyright: ignore [reportArgumentType]
2661
2968
  # Recurse into iterable values (or dict keys).
2662
2969
  var_datas.extend(_extract_var_data(sub))
2663
2970
 
@@ -2668,23 +2975,10 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]:
2668
2975
  # Recurse when value is a dict itself.
2669
2976
  values = getattr(value, "values", None)
2670
2977
  if callable(values):
2671
- var_datas.extend(_extract_var_data(values()))
2978
+ var_datas.extend(_extract_var_data(values())) # pyright: ignore [reportArgumentType]
2672
2979
  return var_datas
2673
2980
 
2674
2981
 
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
2982
  dispatchers: Dict[GenericType, Callable[[Var], Var]] = {}
2689
2983
 
2690
2984
 
@@ -2765,7 +3059,7 @@ def generic_type_to_actual_type_map(
2765
3059
  # call recursively for nested generic types and merge the results
2766
3060
  return {
2767
3061
  k: v
2768
- for generic_arg, actual_arg in zip(generic_args, actual_args)
3062
+ for generic_arg, actual_arg in zip(generic_args, actual_args, strict=True)
2769
3063
  for k, v in generic_type_to_actual_type_map(generic_arg, actual_arg).items()
2770
3064
  }
2771
3065
 
@@ -2922,13 +3216,22 @@ def dispatch(
2922
3216
 
2923
3217
  V = TypeVar("V")
2924
3218
 
2925
- BASE_TYPE = TypeVar("BASE_TYPE", bound=Base)
3219
+ BASE_TYPE = TypeVar("BASE_TYPE", bound=Base | None)
3220
+ SQLA_TYPE = TypeVar("SQLA_TYPE", bound=DeclarativeBase | None)
2926
3221
 
3222
+ if TYPE_CHECKING:
3223
+ from _typeshed import DataclassInstance
2927
3224
 
2928
- class Field(Generic[T]):
3225
+ DATACLASS_TYPE = TypeVar("DATACLASS_TYPE", bound=DataclassInstance | None)
3226
+
3227
+ FIELD_TYPE = TypeVar("FIELD_TYPE")
3228
+ MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping | None)
3229
+
3230
+
3231
+ class Field(Generic[FIELD_TYPE]):
2929
3232
  """Shadow class for Var to allow for type hinting in the IDE."""
2930
3233
 
2931
- def __set__(self, instance, value: T):
3234
+ def __set__(self, instance: Any, value: FIELD_TYPE):
2932
3235
  """Set the Var.
2933
3236
 
2934
3237
  Args:
@@ -2937,41 +3240,55 @@ class Field(Generic[T]):
2937
3240
  """
2938
3241
 
2939
3242
  @overload
2940
- def __get__(self: Field[bool], instance: None, owner) -> BooleanVar: ...
3243
+ def __get__(self: Field[bool], instance: None, owner: Any) -> BooleanVar: ...
2941
3244
 
2942
3245
  @overload
2943
- def __get__(self: Field[int], instance: None, owner) -> NumberVar: ...
3246
+ def __get__(
3247
+ self: Field[int] | Field[float] | Field[int | float], instance: None, owner: Any
3248
+ ) -> NumberVar: ...
2944
3249
 
2945
3250
  @overload
2946
- def __get__(self: Field[str], instance: None, owner) -> StringVar: ...
3251
+ def __get__(self: Field[str], instance: None, owner: Any) -> StringVar: ...
2947
3252
 
2948
3253
  @overload
2949
- def __get__(self: Field[None], instance: None, owner) -> NoneVar: ...
3254
+ def __get__(self: Field[None], instance: None, owner: Any) -> NoneVar: ...
2950
3255
 
2951
3256
  @overload
2952
3257
  def __get__(
2953
3258
  self: Field[List[V]] | Field[Set[V]] | Field[Tuple[V, ...]],
2954
3259
  instance: None,
2955
- owner,
3260
+ owner: Any,
2956
3261
  ) -> ArrayVar[List[V]]: ...
2957
3262
 
2958
3263
  @overload
2959
3264
  def __get__(
2960
- self: Field[Dict[str, V]], instance: None, owner
2961
- ) -> ObjectVar[Dict[str, V]]: ...
3265
+ self: Field[MAPPING_TYPE], instance: None, owner: Any
3266
+ ) -> ObjectVar[MAPPING_TYPE]: ...
2962
3267
 
2963
3268
  @overload
2964
3269
  def __get__(
2965
- self: Field[BASE_TYPE], instance: None, owner
3270
+ self: Field[BASE_TYPE], instance: None, owner: Any
2966
3271
  ) -> ObjectVar[BASE_TYPE]: ...
2967
3272
 
2968
3273
  @overload
2969
- def __get__(self, instance: None, owner) -> Var[T]: ...
3274
+ def __get__(
3275
+ self: Field[SQLA_TYPE], instance: None, owner: Any
3276
+ ) -> ObjectVar[SQLA_TYPE]: ...
3277
+
3278
+ if TYPE_CHECKING:
3279
+
3280
+ @overload
3281
+ def __get__(
3282
+ self: Field[DATACLASS_TYPE], instance: None, owner: Any
3283
+ ) -> ObjectVar[DATACLASS_TYPE]: ...
3284
+
3285
+ @overload
3286
+ def __get__(self, instance: None, owner: Any) -> Var[FIELD_TYPE]: ...
2970
3287
 
2971
3288
  @overload
2972
- def __get__(self, instance, owner) -> T: ...
3289
+ def __get__(self, instance: Any, owner: Any) -> FIELD_TYPE: ...
2973
3290
 
2974
- def __get__(self, instance, owner): # type: ignore
3291
+ def __get__(self, instance: Any, owner: Any): # pyright: ignore [reportInconsistentOverload]
2975
3292
  """Get the Var.
2976
3293
 
2977
3294
  Args:
@@ -2980,7 +3297,7 @@ class Field(Generic[T]):
2980
3297
  """
2981
3298
 
2982
3299
 
2983
- def field(value: T) -> Field[T]:
3300
+ def field(value: FIELD_TYPE) -> Field[FIELD_TYPE]:
2984
3301
  """Create a Field with a value.
2985
3302
 
2986
3303
  Args:
@@ -2989,4 +3306,4 @@ def field(value: T) -> Field[T]:
2989
3306
  Returns:
2990
3307
  The Field.
2991
3308
  """
2992
- return value # type: ignore
3309
+ return value # pyright: ignore [reportReturnType]