reflex 0.7.1a4__py3-none-any.whl → 0.7.2a2__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 (227) hide show
  1. reflex/.templates/jinja/web/utils/context.js.jinja2 +8 -8
  2. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -3
  3. reflex/.templates/web/utils/state.js +18 -18
  4. reflex/admin.py +1 -2
  5. reflex/app.py +46 -49
  6. reflex/app_mixins/lifespan.py +2 -2
  7. reflex/app_mixins/middleware.py +1 -2
  8. reflex/assets.py +1 -2
  9. reflex/base.py +2 -2
  10. reflex/compiler/compiler.py +51 -16
  11. reflex/compiler/utils.py +4 -13
  12. reflex/components/base/app_wrap.pyi +7 -7
  13. reflex/components/base/bare.py +3 -3
  14. reflex/components/base/body.pyi +7 -7
  15. reflex/components/base/document.py +1 -3
  16. reflex/components/base/document.pyi +32 -32
  17. reflex/components/base/error_boundary.py +2 -4
  18. reflex/components/base/error_boundary.pyi +11 -13
  19. reflex/components/base/fragment.pyi +7 -7
  20. reflex/components/base/head.pyi +13 -13
  21. reflex/components/base/link.pyi +22 -22
  22. reflex/components/base/meta.py +5 -7
  23. reflex/components/base/meta.pyi +40 -40
  24. reflex/components/base/script.pyi +11 -14
  25. reflex/components/base/strict_mode.pyi +7 -7
  26. reflex/components/component.py +188 -113
  27. reflex/components/core/auto_scroll.py +8 -1
  28. reflex/components/core/auto_scroll.pyi +183 -210
  29. reflex/components/core/banner.py +2 -4
  30. reflex/components/core/banner.pyi +390 -444
  31. reflex/components/core/breakpoints.py +5 -5
  32. reflex/components/core/client_side_routing.pyi +14 -14
  33. reflex/components/core/clipboard.py +4 -4
  34. reflex/components/core/clipboard.pyi +12 -14
  35. reflex/components/core/cond.py +17 -25
  36. reflex/components/core/debounce.py +3 -3
  37. reflex/components/core/debounce.pyi +14 -14
  38. reflex/components/core/foreach.py +7 -2
  39. reflex/components/core/html.py +1 -3
  40. reflex/components/core/html.pyi +184 -213
  41. reflex/components/core/match.py +15 -19
  42. reflex/components/core/sticky.pyi +930 -1078
  43. reflex/components/core/upload.py +4 -4
  44. reflex/components/core/upload.pyi +62 -62
  45. reflex/components/datadisplay/code.py +6 -6
  46. reflex/components/datadisplay/code.pyi +1159 -1165
  47. reflex/components/datadisplay/dataeditor.py +49 -49
  48. reflex/components/datadisplay/dataeditor.pyi +95 -123
  49. reflex/components/datadisplay/logo.py +1 -3
  50. reflex/components/datadisplay/shiki_code_block.py +8 -10
  51. reflex/components/datadisplay/shiki_code_block.pyi +1678 -1720
  52. reflex/components/el/element.pyi +7 -7
  53. reflex/components/el/elements/base.pyi +183 -210
  54. reflex/components/el/elements/forms.py +24 -24
  55. reflex/components/el/elements/forms.pyi +2572 -2934
  56. reflex/components/el/elements/inline.py +4 -4
  57. reflex/components/el/elements/inline.pyi +5191 -5953
  58. reflex/components/el/elements/media.py +47 -47
  59. reflex/components/el/elements/media.pyi +4802 -5500
  60. reflex/components/el/elements/metadata.py +1 -3
  61. reflex/components/el/elements/metadata.pyi +782 -896
  62. reflex/components/el/elements/other.pyi +1278 -1467
  63. reflex/components/el/elements/scripts.pyi +580 -667
  64. reflex/components/el/elements/sectioning.pyi +2761 -3166
  65. reflex/components/el/elements/tables.pyi +1840 -2119
  66. reflex/components/el/elements/typography.pyi +2772 -3179
  67. reflex/components/gridjs/datatable.py +7 -7
  68. reflex/components/gridjs/datatable.pyi +19 -19
  69. reflex/components/lucide/icon.pyi +21 -21
  70. reflex/components/markdown/markdown.py +2 -2
  71. reflex/components/markdown/markdown.pyi +9 -9
  72. reflex/components/moment/moment.py +11 -12
  73. reflex/components/moment/moment.pyi +44 -47
  74. reflex/components/next/base.pyi +7 -7
  75. reflex/components/next/image.py +3 -3
  76. reflex/components/next/image.pyi +19 -21
  77. reflex/components/next/link.pyi +9 -9
  78. reflex/components/next/video.py +1 -3
  79. reflex/components/next/video.pyi +9 -9
  80. reflex/components/plotly/plotly.py +22 -45
  81. reflex/components/plotly/plotly.pyi +164 -164
  82. reflex/components/radix/primitives/accordion.py +14 -14
  83. reflex/components/radix/primitives/accordion.pyi +439 -487
  84. reflex/components/radix/primitives/base.py +1 -3
  85. reflex/components/radix/primitives/base.pyi +15 -15
  86. reflex/components/radix/primitives/drawer.py +3 -3
  87. reflex/components/radix/primitives/drawer.pyi +110 -116
  88. reflex/components/radix/primitives/form.py +1 -1
  89. reflex/components/radix/primitives/form.pyi +668 -752
  90. reflex/components/radix/primitives/progress.py +6 -6
  91. reflex/components/radix/primitives/progress.pyi +225 -243
  92. reflex/components/radix/primitives/slider.py +6 -6
  93. reflex/components/radix/primitives/slider.pyi +52 -55
  94. reflex/components/radix/themes/base.py +3 -6
  95. reflex/components/radix/themes/base.pyi +197 -303
  96. reflex/components/radix/themes/color_mode.py +5 -5
  97. reflex/components/radix/themes/color_mode.pyi +366 -436
  98. reflex/components/radix/themes/components/alert_dialog.pyi +229 -262
  99. reflex/components/radix/themes/components/aspect_ratio.py +1 -3
  100. reflex/components/radix/themes/components/aspect_ratio.pyi +8 -8
  101. reflex/components/radix/themes/components/avatar.pyi +79 -94
  102. reflex/components/radix/themes/components/badge.pyi +252 -295
  103. reflex/components/radix/themes/components/button.pyi +269 -314
  104. reflex/components/radix/themes/components/callout.py +2 -2
  105. reflex/components/radix/themes/components/callout.pyi +1116 -1290
  106. reflex/components/radix/themes/components/card.pyi +194 -229
  107. reflex/components/radix/themes/components/checkbox.pyi +243 -278
  108. reflex/components/radix/themes/components/checkbox_cards.py +3 -7
  109. reflex/components/radix/themes/components/checkbox_cards.pyi +101 -135
  110. reflex/components/radix/themes/components/checkbox_group.py +2 -2
  111. reflex/components/radix/themes/components/checkbox_group.pyi +83 -96
  112. reflex/components/radix/themes/components/context_menu.py +18 -15
  113. reflex/components/radix/themes/components/context_menu.pyi +408 -458
  114. reflex/components/radix/themes/components/data_list.pyi +122 -147
  115. reflex/components/radix/themes/components/dialog.pyi +231 -264
  116. reflex/components/radix/themes/components/dropdown_menu.py +16 -13
  117. reflex/components/radix/themes/components/dropdown_menu.pyi +223 -246
  118. reflex/components/radix/themes/components/hover_card.py +2 -2
  119. reflex/components/radix/themes/components/hover_card.pyi +237 -282
  120. reflex/components/radix/themes/components/icon_button.pyi +269 -314
  121. reflex/components/radix/themes/components/inset.py +8 -8
  122. reflex/components/radix/themes/components/inset.pyi +232 -292
  123. reflex/components/radix/themes/components/popover.py +2 -2
  124. reflex/components/radix/themes/components/popover.pyi +229 -271
  125. reflex/components/radix/themes/components/progress.pyi +80 -96
  126. reflex/components/radix/themes/components/radio.pyi +73 -86
  127. reflex/components/radix/themes/components/radio_cards.py +4 -8
  128. reflex/components/radix/themes/components/radio_cards.pyi +117 -154
  129. reflex/components/radix/themes/components/radio_group.py +3 -3
  130. reflex/components/radix/themes/components/radio_group.pyi +250 -291
  131. reflex/components/radix/themes/components/scroll_area.pyi +14 -20
  132. reflex/components/radix/themes/components/segmented_control.py +6 -6
  133. reflex/components/radix/themes/components/segmented_control.pyi +89 -108
  134. reflex/components/radix/themes/components/select.py +7 -7
  135. reflex/components/radix/themes/components/select.pyi +376 -444
  136. reflex/components/radix/themes/components/separator.pyi +79 -93
  137. reflex/components/radix/themes/components/skeleton.pyi +32 -26
  138. reflex/components/radix/themes/components/slider.py +8 -8
  139. reflex/components/radix/themes/components/slider.pyi +99 -122
  140. reflex/components/radix/themes/components/spinner.pyi +12 -19
  141. reflex/components/radix/themes/components/switch.pyi +84 -99
  142. reflex/components/radix/themes/components/table.py +9 -9
  143. reflex/components/radix/themes/components/table.pyi +1440 -1794
  144. reflex/components/radix/themes/components/tabs.py +4 -4
  145. reflex/components/radix/themes/components/tabs.pyi +120 -132
  146. reflex/components/radix/themes/components/text_area.pyi +281 -331
  147. reflex/components/radix/themes/components/text_field.py +2 -2
  148. reflex/components/radix/themes/components/text_field.pyi +639 -734
  149. reflex/components/radix/themes/components/tooltip.py +6 -6
  150. reflex/components/radix/themes/components/tooltip.pyi +34 -43
  151. reflex/components/radix/themes/layout/base.pyi +85 -182
  152. reflex/components/radix/themes/layout/box.pyi +183 -210
  153. reflex/components/radix/themes/layout/center.pyi +225 -286
  154. reflex/components/radix/themes/layout/container.pyi +191 -224
  155. reflex/components/radix/themes/layout/flex.py +2 -2
  156. reflex/components/radix/themes/layout/flex.pyi +225 -286
  157. reflex/components/radix/themes/layout/grid.py +2 -2
  158. reflex/components/radix/themes/layout/grid.pyi +245 -315
  159. reflex/components/radix/themes/layout/list.py +2 -2
  160. reflex/components/radix/themes/layout/list.pyi +712 -815
  161. reflex/components/radix/themes/layout/section.pyi +187 -221
  162. reflex/components/radix/themes/layout/spacer.pyi +225 -286
  163. reflex/components/radix/themes/layout/stack.pyi +625 -768
  164. reflex/components/radix/themes/typography/blockquote.pyi +257 -299
  165. reflex/components/radix/themes/typography/code.pyi +259 -304
  166. reflex/components/radix/themes/typography/heading.pyi +272 -324
  167. reflex/components/radix/themes/typography/link.pyi +302 -358
  168. reflex/components/radix/themes/typography/text.pyi +1669 -1945
  169. reflex/components/react_player/audio.pyi +20 -22
  170. reflex/components/react_player/react_player.pyi +19 -19
  171. reflex/components/react_player/video.pyi +20 -22
  172. reflex/components/recharts/cartesian.py +100 -97
  173. reflex/components/recharts/cartesian.pyi +891 -1007
  174. reflex/components/recharts/charts.py +42 -42
  175. reflex/components/recharts/charts.pyi +212 -249
  176. reflex/components/recharts/general.py +22 -21
  177. reflex/components/recharts/general.pyi +198 -223
  178. reflex/components/recharts/polar.py +42 -45
  179. reflex/components/recharts/polar.pyi +254 -288
  180. reflex/components/recharts/recharts.pyi +13 -13
  181. reflex/components/sonner/toast.py +20 -20
  182. reflex/components/sonner/toast.pyi +58 -61
  183. reflex/components/suneditor/editor.py +9 -9
  184. reflex/components/suneditor/editor.pyi +78 -83
  185. reflex/components/tags/cond_tag.py +2 -2
  186. reflex/components/tags/iter_tag.py +10 -14
  187. reflex/components/tags/match_tag.py +2 -2
  188. reflex/components/tags/tag.py +10 -10
  189. reflex/config.py +36 -35
  190. reflex/constants/__init__.py +56 -53
  191. reflex/custom_components/custom_components.py +6 -7
  192. reflex/event.py +38 -42
  193. reflex/experimental/client_state.py +2 -4
  194. reflex/experimental/layout.py +2 -2
  195. reflex/experimental/layout.pyi +579 -663
  196. reflex/istate/data.py +4 -5
  197. reflex/middleware/hydrate_middleware.py +2 -2
  198. reflex/middleware/middleware.py +2 -2
  199. reflex/model.py +3 -5
  200. reflex/page.py +2 -2
  201. reflex/reflex.py +9 -10
  202. reflex/state.py +77 -49
  203. reflex/style.py +9 -3
  204. reflex/testing.py +21 -24
  205. reflex/utils/console.py +1 -1
  206. reflex/utils/decorator.py +26 -1
  207. reflex/utils/exec.py +6 -11
  208. reflex/utils/export.py +2 -3
  209. reflex/utils/format.py +4 -4
  210. reflex/utils/imports.py +12 -12
  211. reflex/utils/prerequisites.py +35 -84
  212. reflex/utils/processes.py +5 -5
  213. reflex/utils/pyi_generator.py +33 -22
  214. reflex/utils/serializers.py +60 -15
  215. reflex/utils/types.py +237 -56
  216. reflex/vars/base.py +122 -72
  217. reflex/vars/datetime.py +2 -2
  218. reflex/vars/function.py +52 -55
  219. reflex/vars/number.py +59 -5
  220. reflex/vars/object.py +57 -26
  221. reflex/vars/sequence.py +983 -958
  222. {reflex-0.7.1a4.dist-info → reflex-0.7.2a2.dist-info}/METADATA +3 -6
  223. reflex-0.7.2a2.dist-info/RECORD +405 -0
  224. {reflex-0.7.1a4.dist-info → reflex-0.7.2a2.dist-info}/WHEEL +1 -1
  225. reflex-0.7.1a4.dist-info/RECORD +0 -405
  226. {reflex-0.7.1a4.dist-info → reflex-0.7.2a2.dist-info}/LICENSE +0 -0
  227. {reflex-0.7.1a4.dist-info → reflex-0.7.2a2.dist-info}/entry_points.txt +0 -0
reflex/utils/types.py CHANGED
@@ -14,11 +14,13 @@ from typing import (
14
14
  Callable,
15
15
  ClassVar,
16
16
  Dict,
17
+ ForwardRef,
17
18
  FrozenSet,
18
19
  Iterable,
19
20
  List,
20
21
  Literal,
21
22
  Mapping,
23
+ NoReturn,
22
24
  Optional,
23
25
  Sequence,
24
26
  Tuple,
@@ -27,9 +29,9 @@ from typing import (
27
29
  _GenericAlias, # pyright: ignore [reportAttributeAccessIssue]
28
30
  _SpecialGenericAlias, # pyright: ignore [reportAttributeAccessIssue]
29
31
  get_args,
30
- get_type_hints,
31
32
  )
32
33
  from typing import get_origin as get_origin_og
34
+ from typing import get_type_hints as get_type_hints_og
33
35
 
34
36
  import sqlalchemy
35
37
  from pydantic.v1.fields import ModelField
@@ -49,18 +51,18 @@ from reflex.utils import console
49
51
  # Potential GenericAlias types for isinstance checks.
50
52
  GenericAliasTypes = (_GenericAlias, GenericAlias, _SpecialGenericAlias)
51
53
 
52
- # Potential Union types for isinstance checks (UnionType added in py3.10).
53
- UnionTypes = (Union, types.UnionType) if hasattr(types, "UnionType") else (Union,)
54
+ # Potential Union types for isinstance checks.
55
+ UnionTypes = (Union, types.UnionType)
54
56
 
55
57
  # Union of generic types.
56
- GenericType = Union[Type, _GenericAlias]
58
+ GenericType = Type | _GenericAlias
57
59
 
58
60
  # Valid state var types.
59
61
  JSONType = {str, int, float, bool}
60
62
  PrimitiveType = Union[int, float, bool, str, list, dict, set, tuple]
61
63
  PrimitiveTypes = (int, float, bool, str, list, dict, set, tuple)
62
- StateVar = Union[PrimitiveType, Base, None]
63
- StateIterVar = Union[list, set, tuple]
64
+ StateVar = PrimitiveType | Base | None
65
+ StateIterVar = list | set | tuple
64
66
 
65
67
  if TYPE_CHECKING:
66
68
  from reflex.vars.base import Var
@@ -76,7 +78,7 @@ if TYPE_CHECKING:
76
78
  | Callable[[Var, Var, Var, Var, Var, Var, Var], Sequence[Var]]
77
79
  )
78
80
  else:
79
- ArgsSpec = Callable[..., List[Any]]
81
+ ArgsSpec = Callable[..., list[Any]]
80
82
 
81
83
 
82
84
  PrimitiveToAnnotation = {
@@ -141,15 +143,20 @@ def is_generic_alias(cls: GenericType) -> bool:
141
143
  return isinstance(cls, GenericAliasTypes) # pyright: ignore [reportArgumentType]
142
144
 
143
145
 
144
- def unionize(*args: GenericType) -> Type:
145
- """Unionize the types.
146
+ @lru_cache()
147
+ def get_type_hints(obj: Any) -> Dict[str, Any]:
148
+ """Get the type hints of a class.
146
149
 
147
150
  Args:
148
- args: The types to unionize.
151
+ obj: The class to get the type hints of.
149
152
 
150
153
  Returns:
151
- The unionized types.
154
+ The type hints of the class.
152
155
  """
156
+ return get_type_hints_og(obj)
157
+
158
+
159
+ def _unionize(args: list[GenericType]) -> Type:
153
160
  if not args:
154
161
  return Any # pyright: ignore [reportReturnType]
155
162
  if len(args) == 1:
@@ -161,6 +168,18 @@ def unionize(*args: GenericType) -> Type:
161
168
  return Union[unionize(*first_half), unionize(*second_half)] # pyright: ignore [reportReturnType]
162
169
 
163
170
 
171
+ def unionize(*args: GenericType) -> Type:
172
+ """Unionize the types.
173
+
174
+ Args:
175
+ args: The types to unionize.
176
+
177
+ Returns:
178
+ The unionized types.
179
+ """
180
+ return _unionize([arg for arg in args if arg is not NoReturn])
181
+
182
+
164
183
  def is_none(cls: GenericType) -> bool:
165
184
  """Check if a class is None.
166
185
 
@@ -232,6 +251,33 @@ def is_optional(cls: GenericType) -> bool:
232
251
  return is_union(cls) and type(None) in get_args(cls)
233
252
 
234
253
 
254
+ def true_type_for_pydantic_field(f: ModelField):
255
+ """Get the type for a pydantic field.
256
+
257
+ Args:
258
+ f: The field to get the type for.
259
+
260
+ Returns:
261
+ The type for the field.
262
+ """
263
+ if not isinstance(f.annotation, (str, ForwardRef)):
264
+ return f.annotation
265
+
266
+ type_ = f.outer_type_
267
+
268
+ if (
269
+ f.field_info.default is None
270
+ or (isinstance(f.annotation, str) and f.annotation.startswith("Optional"))
271
+ or (
272
+ isinstance(f.annotation, ForwardRef)
273
+ and f.annotation.__forward_arg__.startswith("Optional")
274
+ )
275
+ ) and not is_optional(type_):
276
+ return Optional[type_]
277
+
278
+ return type_
279
+
280
+
235
281
  def value_inside_optional(cls: GenericType) -> GenericType:
236
282
  """Get the value inside an Optional type or the original type.
237
283
 
@@ -242,10 +288,33 @@ def value_inside_optional(cls: GenericType) -> GenericType:
242
288
  The value inside the Optional type or the original type.
243
289
  """
244
290
  if is_union(cls) and len(args := get_args(cls)) >= 2 and type(None) in args:
291
+ if len(args) == 2:
292
+ return args[0] if args[1] is type(None) else args[1]
245
293
  return unionize(*[arg for arg in args if arg is not type(None)])
246
294
  return cls
247
295
 
248
296
 
297
+ def get_field_type(cls: GenericType, field_name: str) -> GenericType | None:
298
+ """Get the type of a field in a class.
299
+
300
+ Args:
301
+ cls: The class to check.
302
+ field_name: The name of the field to check.
303
+
304
+ Returns:
305
+ The type of the field, if it exists, else None.
306
+ """
307
+ if (
308
+ hasattr(cls, "__fields__")
309
+ and field_name in cls.__fields__
310
+ and hasattr(cls.__fields__[field_name], "annotation")
311
+ and not isinstance(cls.__fields__[field_name].annotation, (str, ForwardRef))
312
+ ):
313
+ return cls.__fields__[field_name].annotation
314
+ type_hints = get_type_hints(cls)
315
+ return type_hints.get(field_name, None)
316
+
317
+
249
318
  def get_property_hint(attr: Any | None) -> GenericType | None:
250
319
  """Check if an attribute is a property and return its type hint.
251
320
 
@@ -283,24 +352,9 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
283
352
  if hint := get_property_hint(attr):
284
353
  return hint
285
354
 
286
- if (
287
- hasattr(cls, "__fields__")
288
- and name in cls.__fields__
289
- and hasattr(cls.__fields__[name], "outer_type_")
290
- ):
355
+ if hasattr(cls, "__fields__") and name in cls.__fields__:
291
356
  # pydantic models
292
- field = cls.__fields__[name]
293
- type_ = field.outer_type_
294
- if isinstance(type_, ModelField):
295
- type_ = type_.type_
296
- if (
297
- not field.required
298
- and field.default is None
299
- and field.default_factory is None
300
- ):
301
- # Ensure frontend uses null coalescing when accessing.
302
- type_ = Optional[type_]
303
- return type_
357
+ return get_field_type(cls, name)
304
358
  elif isinstance(cls, type) and issubclass(cls, DeclarativeBase):
305
359
  insp = sqlalchemy.inspect(cls)
306
360
  if name in insp.columns:
@@ -322,7 +376,7 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
322
376
  type_ = PrimitiveToAnnotation[type_]
323
377
  type_ = type_[item_type] # pyright: ignore [reportIndexIssue]
324
378
  if column.nullable:
325
- type_ = Optional[type_]
379
+ type_ = type_ | None
326
380
  return type_
327
381
  if name in insp.all_orm_descriptors:
328
382
  descriptor = insp.all_orm_descriptors[name]
@@ -333,10 +387,10 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
333
387
  if isinstance(prop, Relationship):
334
388
  type_ = prop.mapper.class_
335
389
  # TODO: check for nullable?
336
- type_ = List[type_] if prop.uselist else Optional[type_]
390
+ type_ = list[type_] if prop.uselist else type_ | None
337
391
  return type_
338
392
  if isinstance(attr, AssociationProxyInstance):
339
- return List[
393
+ return list[
340
394
  get_attribute_access_type(
341
395
  attr.target_class,
342
396
  attr.remote_attr.key, # type: ignore[attr-defined]
@@ -510,13 +564,22 @@ def does_obj_satisfy_typed_dict(obj: Any, cls: GenericType) -> bool:
510
564
  return required_keys.issubset(required_keys)
511
565
 
512
566
 
513
- def _isinstance(obj: Any, cls: GenericType, nested: int = 0) -> bool:
567
+ def _isinstance(
568
+ obj: Any,
569
+ cls: GenericType,
570
+ *,
571
+ nested: int = 0,
572
+ treat_var_as_type: bool = True,
573
+ treat_mutable_obj_as_immutable: bool = False,
574
+ ) -> bool:
514
575
  """Check if an object is an instance of a class.
515
576
 
516
577
  Args:
517
578
  obj: The object to check.
518
579
  cls: The class to check against.
519
580
  nested: How many levels deep to check.
581
+ treat_var_as_type: Whether to treat Var as the type it represents, i.e. _var_type.
582
+ treat_mutable_obj_as_immutable: Whether to treat mutable objects as immutable. Useful if a component declares a mutable object as a prop, but the value is not expected to change.
520
583
 
521
584
  Returns:
522
585
  Whether the object is an instance of the class.
@@ -529,15 +592,26 @@ def _isinstance(obj: Any, cls: GenericType, nested: int = 0) -> bool:
529
592
  if cls is Var:
530
593
  return isinstance(obj, Var)
531
594
  if isinstance(obj, LiteralVar):
532
- return _isinstance(obj._var_value, cls, nested=nested)
595
+ return treat_var_as_type and _isinstance(
596
+ obj._var_value, cls, nested=nested, treat_var_as_type=True
597
+ )
533
598
  if isinstance(obj, Var):
534
- return _issubclass(obj._var_type, cls)
599
+ return treat_var_as_type and typehint_issubclass(
600
+ obj._var_type,
601
+ cls,
602
+ treat_mutable_superclasss_as_immutable=treat_mutable_obj_as_immutable,
603
+ treat_literals_as_union_of_types=True,
604
+ treat_any_as_subtype_of_everything=True,
605
+ )
535
606
 
536
607
  if cls is None or cls is type(None):
537
608
  return obj is None
538
609
 
539
610
  if cls and is_union(cls):
540
- return any(_isinstance(obj, arg, nested=nested) for arg in get_args(cls))
611
+ return any(
612
+ _isinstance(obj, arg, nested=nested, treat_var_as_type=treat_var_as_type)
613
+ for arg in get_args(cls)
614
+ )
541
615
 
542
616
  if is_literal(cls):
543
617
  return obj in get_args(cls)
@@ -561,43 +635,87 @@ def _isinstance(obj: Any, cls: GenericType, nested: int = 0) -> bool:
561
635
  args = get_args(cls)
562
636
 
563
637
  if not args:
638
+ if treat_mutable_obj_as_immutable:
639
+ if origin is dict:
640
+ origin = Mapping
641
+ elif origin is list or origin is set:
642
+ origin = Sequence
564
643
  # cls is a simple generic class
565
644
  return isinstance(obj, origin)
566
645
 
567
646
  if nested > 0 and args:
568
647
  if origin is list:
569
- return isinstance(obj, list) and all(
570
- _isinstance(item, args[0], nested=nested - 1) for item in obj
648
+ expected_class = Sequence if treat_mutable_obj_as_immutable else list
649
+ return isinstance(obj, expected_class) and all(
650
+ _isinstance(
651
+ item,
652
+ args[0],
653
+ nested=nested - 1,
654
+ treat_var_as_type=treat_var_as_type,
655
+ )
656
+ for item in obj
571
657
  )
572
658
  if origin is tuple:
573
659
  if args[-1] is Ellipsis:
574
660
  return isinstance(obj, tuple) and all(
575
- _isinstance(item, args[0], nested=nested - 1) for item in obj
661
+ _isinstance(
662
+ item,
663
+ args[0],
664
+ nested=nested - 1,
665
+ treat_var_as_type=treat_var_as_type,
666
+ )
667
+ for item in obj
576
668
  )
577
669
  return (
578
670
  isinstance(obj, tuple)
579
671
  and len(obj) == len(args)
580
672
  and all(
581
- _isinstance(item, arg, nested=nested - 1)
673
+ _isinstance(
674
+ item,
675
+ arg,
676
+ nested=nested - 1,
677
+ treat_var_as_type=treat_var_as_type,
678
+ )
582
679
  for item, arg in zip(obj, args, strict=True)
583
680
  )
584
681
  )
585
682
  if origin in (dict, Mapping, Breakpoints):
586
- return isinstance(obj, Mapping) and all(
587
- _isinstance(key, args[0], nested=nested - 1)
588
- and _isinstance(value, args[1], nested=nested - 1)
683
+ expected_class = (
684
+ dict
685
+ if origin is dict and not treat_mutable_obj_as_immutable
686
+ else Mapping
687
+ )
688
+ return isinstance(obj, expected_class) and all(
689
+ _isinstance(
690
+ key, args[0], nested=nested - 1, treat_var_as_type=treat_var_as_type
691
+ )
692
+ and _isinstance(
693
+ value,
694
+ args[1],
695
+ nested=nested - 1,
696
+ treat_var_as_type=treat_var_as_type,
697
+ )
589
698
  for key, value in obj.items()
590
699
  )
591
700
  if origin is set:
592
- return isinstance(obj, set) and all(
593
- _isinstance(item, args[0], nested=nested - 1) for item in obj
701
+ expected_class = Sequence if treat_mutable_obj_as_immutable else set
702
+ return isinstance(obj, expected_class) and all(
703
+ _isinstance(
704
+ item,
705
+ args[0],
706
+ nested=nested - 1,
707
+ treat_var_as_type=treat_var_as_type,
708
+ )
709
+ for item in obj
594
710
  )
595
711
 
596
712
  if args:
597
713
  from reflex.vars import Field
598
714
 
599
715
  if origin is Field:
600
- return _isinstance(obj, args[0], nested=nested)
716
+ return _isinstance(
717
+ obj, args[0], nested=nested, treat_var_as_type=treat_var_as_type
718
+ )
601
719
 
602
720
  return isinstance(obj, get_base_class(cls))
603
721
 
@@ -805,7 +923,7 @@ StateBases = get_base_class(StateVar)
805
923
  StateIterBases = get_base_class(StateIterVar)
806
924
 
807
925
 
808
- def safe_issubclass(cls: Type, cls_check: Type | Tuple[Type, ...]):
926
+ def safe_issubclass(cls: Type, cls_check: Type | tuple[Type, ...]):
809
927
  """Check if a class is a subclass of another class. Returns False if internal error occurs.
810
928
 
811
929
  Args:
@@ -821,12 +939,22 @@ def safe_issubclass(cls: Type, cls_check: Type | Tuple[Type, ...]):
821
939
  return False
822
940
 
823
941
 
824
- def typehint_issubclass(possible_subclass: Any, possible_superclass: Any) -> bool:
942
+ def typehint_issubclass(
943
+ possible_subclass: Any,
944
+ possible_superclass: Any,
945
+ *,
946
+ treat_mutable_superclasss_as_immutable: bool = False,
947
+ treat_literals_as_union_of_types: bool = True,
948
+ treat_any_as_subtype_of_everything: bool = False,
949
+ ) -> bool:
825
950
  """Check if a type hint is a subclass of another type hint.
826
951
 
827
952
  Args:
828
953
  possible_subclass: The type hint to check.
829
954
  possible_superclass: The type hint to check against.
955
+ treat_mutable_superclasss_as_immutable: Whether to treat target classes as immutable.
956
+ treat_literals_as_union_of_types: Whether to treat literals as a union of their types.
957
+ treat_any_as_subtype_of_everything: Whether to treat Any as a subtype of everything. This is the default behavior in Python.
830
958
 
831
959
  Returns:
832
960
  Whether the type hint is a subclass of the other type hint.
@@ -834,7 +962,9 @@ def typehint_issubclass(possible_subclass: Any, possible_superclass: Any) -> boo
834
962
  if possible_superclass is Any:
835
963
  return True
836
964
  if possible_subclass is Any:
837
- return False
965
+ return treat_any_as_subtype_of_everything
966
+ if possible_subclass is NoReturn:
967
+ return True
838
968
 
839
969
  provided_type_origin = get_origin(possible_subclass)
840
970
  accepted_type_origin = get_origin(possible_superclass)
@@ -843,6 +973,19 @@ def typehint_issubclass(possible_subclass: Any, possible_superclass: Any) -> boo
843
973
  # In this case, we are dealing with a non-generic type, so we can use issubclass
844
974
  return issubclass(possible_subclass, possible_superclass)
845
975
 
976
+ if treat_literals_as_union_of_types and is_literal(possible_superclass):
977
+ args = get_args(possible_superclass)
978
+ return any(
979
+ typehint_issubclass(
980
+ possible_subclass,
981
+ type(arg),
982
+ treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable,
983
+ treat_literals_as_union_of_types=treat_literals_as_union_of_types,
984
+ treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything,
985
+ )
986
+ for arg in args
987
+ )
988
+
846
989
  # Remove this check when Python 3.10 is the minimum supported version
847
990
  if hasattr(types, "UnionType"):
848
991
  provided_type_origin = (
@@ -852,28 +995,60 @@ def typehint_issubclass(possible_subclass: Any, possible_superclass: Any) -> boo
852
995
  Union if accepted_type_origin is types.UnionType else accepted_type_origin
853
996
  )
854
997
 
855
- # Get type arguments (e.g., [float, int] for Dict[float, int])
998
+ # Get type arguments (e.g., [float, int] for dict[float, int])
856
999
  provided_args = get_args(possible_subclass)
857
1000
  accepted_args = get_args(possible_superclass)
858
1001
 
859
1002
  if accepted_type_origin is Union:
860
1003
  if provided_type_origin is not Union:
861
1004
  return any(
862
- typehint_issubclass(possible_subclass, accepted_arg)
1005
+ typehint_issubclass(
1006
+ possible_subclass,
1007
+ accepted_arg,
1008
+ treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable,
1009
+ treat_literals_as_union_of_types=treat_literals_as_union_of_types,
1010
+ treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything,
1011
+ )
863
1012
  for accepted_arg in accepted_args
864
1013
  )
865
1014
  return all(
866
1015
  any(
867
- typehint_issubclass(provided_arg, accepted_arg)
1016
+ typehint_issubclass(
1017
+ provided_arg,
1018
+ accepted_arg,
1019
+ treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable,
1020
+ treat_literals_as_union_of_types=treat_literals_as_union_of_types,
1021
+ treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything,
1022
+ )
868
1023
  for accepted_arg in accepted_args
869
1024
  )
870
1025
  for provided_arg in provided_args
871
1026
  )
1027
+ if provided_type_origin is Union:
1028
+ return all(
1029
+ typehint_issubclass(
1030
+ provided_arg,
1031
+ possible_superclass,
1032
+ treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable,
1033
+ treat_literals_as_union_of_types=treat_literals_as_union_of_types,
1034
+ treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything,
1035
+ )
1036
+ for provided_arg in provided_args
1037
+ )
1038
+
1039
+ provided_type_origin = provided_type_origin or possible_subclass
1040
+ accepted_type_origin = accepted_type_origin or possible_superclass
872
1041
 
873
- # Check if the origin of both types is the same (e.g., list for List[int])
874
- # This probably should be issubclass instead of ==
875
- if (provided_type_origin or possible_subclass) != (
876
- accepted_type_origin or possible_superclass
1042
+ if treat_mutable_superclasss_as_immutable:
1043
+ if accepted_type_origin is dict:
1044
+ accepted_type_origin = Mapping
1045
+ elif accepted_type_origin is list or accepted_type_origin is set:
1046
+ accepted_type_origin = Sequence
1047
+
1048
+ # Check if the origin of both types is the same (e.g., list for list[int])
1049
+ if not safe_issubclass(
1050
+ provided_type_origin or possible_subclass, # pyright: ignore [reportArgumentType]
1051
+ accepted_type_origin or possible_superclass, # pyright: ignore [reportArgumentType]
877
1052
  ):
878
1053
  return False
879
1054
 
@@ -881,7 +1056,13 @@ def typehint_issubclass(possible_subclass: Any, possible_superclass: Any) -> boo
881
1056
  # Note this is not necessarily correct, as it doesn't check against contravariance and covariance
882
1057
  # It also ignores when the length of the arguments is different
883
1058
  return all(
884
- typehint_issubclass(provided_arg, accepted_arg)
1059
+ typehint_issubclass(
1060
+ provided_arg,
1061
+ accepted_arg,
1062
+ treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable,
1063
+ treat_literals_as_union_of_types=treat_literals_as_union_of_types,
1064
+ treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything,
1065
+ )
885
1066
  for provided_arg, accepted_arg in zip(
886
1067
  provided_args, accepted_args, strict=False
887
1068
  )