types.py 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117
  1. """Contains custom types and methods to check types."""
  2. from __future__ import annotations
  3. import dataclasses
  4. import inspect
  5. import types
  6. from collections.abc import Callable, Iterable, Mapping, Sequence
  7. from functools import cached_property, lru_cache, wraps
  8. from types import GenericAlias
  9. from typing import ( # noqa: UP035
  10. TYPE_CHECKING,
  11. Any,
  12. Awaitable,
  13. ClassVar,
  14. Dict,
  15. ForwardRef,
  16. List,
  17. Literal,
  18. MutableMapping,
  19. NoReturn,
  20. Tuple,
  21. Union,
  22. _GenericAlias, # pyright: ignore [reportAttributeAccessIssue]
  23. _SpecialGenericAlias, # pyright: ignore [reportAttributeAccessIssue]
  24. get_args,
  25. is_typeddict,
  26. )
  27. from typing import get_origin as get_origin_og
  28. from typing import get_type_hints as get_type_hints_og
  29. from pydantic.v1.fields import ModelField
  30. from typing_extensions import Self as Self
  31. from typing_extensions import override as override
  32. import reflex
  33. from reflex import constants
  34. from reflex.base import Base
  35. from reflex.components.core.breakpoints import Breakpoints
  36. from reflex.utils import console
  37. # Potential GenericAlias types for isinstance checks.
  38. GenericAliasTypes = (_GenericAlias, GenericAlias, _SpecialGenericAlias)
  39. # Potential Union types for isinstance checks.
  40. UnionTypes = (Union, types.UnionType)
  41. # Union of generic types.
  42. GenericType = type | _GenericAlias
  43. # Valid state var types.
  44. JSONType = {str, int, float, bool}
  45. PrimitiveType = int | float | bool | str | list | dict | set | tuple
  46. PrimitiveTypes = (int, float, bool, str, list, dict, set, tuple)
  47. StateVar = PrimitiveType | Base | None
  48. StateIterVar = list | set | tuple
  49. if TYPE_CHECKING:
  50. from reflex.vars.base import Var
  51. ArgsSpec = (
  52. Callable[[], Sequence[Var]]
  53. | Callable[[Var], Sequence[Var]]
  54. | Callable[[Var, Var], Sequence[Var]]
  55. | Callable[[Var, Var, Var], Sequence[Var]]
  56. | Callable[[Var, Var, Var, Var], Sequence[Var]]
  57. | Callable[[Var, Var, Var, Var, Var], Sequence[Var]]
  58. | Callable[[Var, Var, Var, Var, Var, Var], Sequence[Var]]
  59. | Callable[[Var, Var, Var, Var, Var, Var, Var], Sequence[Var]]
  60. )
  61. else:
  62. ArgsSpec = Callable[..., list[Any]]
  63. Scope = MutableMapping[str, Any]
  64. Message = MutableMapping[str, Any]
  65. Receive = Callable[[], Awaitable[Message]]
  66. Send = Callable[[Message], Awaitable[None]]
  67. ASGIApp = Callable[[Scope, Receive, Send], Awaitable[None]]
  68. PrimitiveToAnnotation = {
  69. list: List, # noqa: UP006
  70. tuple: Tuple, # noqa: UP006
  71. dict: Dict, # noqa: UP006
  72. }
  73. RESERVED_BACKEND_VAR_NAMES = {
  74. "_abc_impl",
  75. "_backend_vars",
  76. "_was_touched",
  77. }
  78. class Unset:
  79. """A class to represent an unset value.
  80. This is used to differentiate between a value that is not set and a value that is set to None.
  81. """
  82. def __repr__(self) -> str:
  83. """Return the string representation of the class.
  84. Returns:
  85. The string representation of the class.
  86. """
  87. return "Unset"
  88. def __bool__(self) -> bool:
  89. """Return False when the class is used in a boolean context.
  90. Returns:
  91. False
  92. """
  93. return False
  94. @lru_cache
  95. def _get_origin_cached(tp: Any):
  96. return get_origin_og(tp)
  97. def get_origin(tp: Any):
  98. """Get the origin of a class.
  99. Args:
  100. tp: The class to get the origin of.
  101. Returns:
  102. The origin of the class.
  103. """
  104. return (
  105. origin
  106. if (origin := getattr(tp, "__origin__", None)) is not None
  107. else _get_origin_cached(tp)
  108. )
  109. @lru_cache
  110. def is_generic_alias(cls: GenericType) -> bool:
  111. """Check whether the class is a generic alias.
  112. Args:
  113. cls: The class to check.
  114. Returns:
  115. Whether the class is a generic alias.
  116. """
  117. return isinstance(cls, GenericAliasTypes)
  118. @lru_cache
  119. def get_type_hints(obj: Any) -> dict[str, Any]:
  120. """Get the type hints of a class.
  121. Args:
  122. obj: The class to get the type hints of.
  123. Returns:
  124. The type hints of the class.
  125. """
  126. return get_type_hints_og(obj)
  127. def _unionize(args: list[GenericType]) -> GenericType:
  128. if not args:
  129. return Any # pyright: ignore [reportReturnType]
  130. if len(args) == 1:
  131. return args[0]
  132. return Union[tuple(args)] # noqa: UP007
  133. def unionize(*args: GenericType) -> type:
  134. """Unionize the types.
  135. Args:
  136. args: The types to unionize.
  137. Returns:
  138. The unionized types.
  139. """
  140. return _unionize([arg for arg in args if arg is not NoReturn])
  141. def is_none(cls: GenericType) -> bool:
  142. """Check if a class is None.
  143. Args:
  144. cls: The class to check.
  145. Returns:
  146. Whether the class is None.
  147. """
  148. return cls is type(None) or cls is None
  149. def is_union(cls: GenericType) -> bool:
  150. """Check if a class is a Union.
  151. Args:
  152. cls: The class to check.
  153. Returns:
  154. Whether the class is a Union.
  155. """
  156. origin = getattr(cls, "__origin__", None)
  157. if origin is Union:
  158. return True
  159. return origin is None and isinstance(cls, types.UnionType)
  160. def is_literal(cls: GenericType) -> bool:
  161. """Check if a class is a Literal.
  162. Args:
  163. cls: The class to check.
  164. Returns:
  165. Whether the class is a literal.
  166. """
  167. return getattr(cls, "__origin__", None) is Literal
  168. def has_args(cls: type) -> bool:
  169. """Check if the class has generic parameters.
  170. Args:
  171. cls: The class to check.
  172. Returns:
  173. Whether the class has generic
  174. """
  175. if get_args(cls):
  176. return True
  177. # Check if the class inherits from a generic class (using __orig_bases__)
  178. if hasattr(cls, "__orig_bases__"):
  179. for base in cls.__orig_bases__:
  180. if get_args(base):
  181. return True
  182. return False
  183. def is_optional(cls: GenericType) -> bool:
  184. """Check if a class is an Optional.
  185. Args:
  186. cls: The class to check.
  187. Returns:
  188. Whether the class is an Optional.
  189. """
  190. return is_union(cls) and type(None) in get_args(cls)
  191. def is_classvar(a_type: Any) -> bool:
  192. """Check if a type is a ClassVar.
  193. Args:
  194. a_type: The type to check.
  195. Returns:
  196. Whether the type is a ClassVar.
  197. """
  198. return a_type is ClassVar or (
  199. type(a_type) is _GenericAlias and a_type.__origin__ is ClassVar
  200. )
  201. def true_type_for_pydantic_field(f: ModelField):
  202. """Get the type for a pydantic field.
  203. Args:
  204. f: The field to get the type for.
  205. Returns:
  206. The type for the field.
  207. """
  208. if not isinstance(f.annotation, (str, ForwardRef)):
  209. return f.annotation
  210. type_ = f.outer_type_
  211. if (
  212. f.field_info.default is None
  213. or (isinstance(f.annotation, str) and f.annotation.startswith("Optional"))
  214. or (
  215. isinstance(f.annotation, ForwardRef)
  216. and f.annotation.__forward_arg__.startswith("Optional")
  217. )
  218. ) and not is_optional(type_):
  219. return type_ | None
  220. return type_
  221. def value_inside_optional(cls: GenericType) -> GenericType:
  222. """Get the value inside an Optional type or the original type.
  223. Args:
  224. cls: The class to check.
  225. Returns:
  226. The value inside the Optional type or the original type.
  227. """
  228. if is_union(cls) and len(args := get_args(cls)) >= 2 and type(None) in args:
  229. if len(args) == 2:
  230. return args[0] if args[1] is type(None) else args[1]
  231. return unionize(*[arg for arg in args if arg is not type(None)])
  232. return cls
  233. def get_field_type(cls: GenericType, field_name: str) -> GenericType | None:
  234. """Get the type of a field in a class.
  235. Args:
  236. cls: The class to check.
  237. field_name: The name of the field to check.
  238. Returns:
  239. The type of the field, if it exists, else None.
  240. """
  241. if (
  242. hasattr(cls, "__fields__")
  243. and field_name in cls.__fields__
  244. and hasattr(cls.__fields__[field_name], "annotation")
  245. and not isinstance(cls.__fields__[field_name].annotation, (str, ForwardRef))
  246. ):
  247. return cls.__fields__[field_name].annotation
  248. type_hints = get_type_hints(cls)
  249. return type_hints.get(field_name, None)
  250. def get_property_hint(attr: Any | None) -> GenericType | None:
  251. """Check if an attribute is a property and return its type hint.
  252. Args:
  253. attr: The descriptor to check.
  254. Returns:
  255. The type hint of the property, if it is a property, else None.
  256. """
  257. from sqlalchemy.ext.hybrid import hybrid_property
  258. if not isinstance(attr, (property, hybrid_property)):
  259. return None
  260. hints = get_type_hints(attr.fget)
  261. return hints.get("return", None)
  262. def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None:
  263. """Check if an attribute can be accessed on the cls and return its type.
  264. Supports pydantic models, unions, and annotated attributes on rx.Model.
  265. Args:
  266. cls: The class to check.
  267. name: The name of the attribute to check.
  268. Returns:
  269. The type of the attribute, if accessible, or None
  270. """
  271. import sqlalchemy
  272. from sqlalchemy.ext.associationproxy import AssociationProxyInstance
  273. from sqlalchemy.orm import DeclarativeBase, Mapped, QueryableAttribute, Relationship
  274. from reflex.model import Model
  275. try:
  276. attr = getattr(cls, name, None)
  277. except NotImplementedError:
  278. attr = None
  279. if hint := get_property_hint(attr):
  280. return hint
  281. if hasattr(cls, "__fields__") and name in cls.__fields__:
  282. # pydantic models
  283. return get_field_type(cls, name)
  284. elif isinstance(cls, type) and issubclass(cls, DeclarativeBase):
  285. insp = sqlalchemy.inspect(cls)
  286. if name in insp.columns:
  287. # check for list types
  288. column = insp.columns[name]
  289. column_type = column.type
  290. try:
  291. type_ = insp.columns[name].type.python_type
  292. except NotImplementedError:
  293. type_ = None
  294. if type_ is not None:
  295. if hasattr(column_type, "item_type"):
  296. try:
  297. item_type = column_type.item_type.python_type # pyright: ignore [reportAttributeAccessIssue]
  298. except NotImplementedError:
  299. item_type = None
  300. if item_type is not None:
  301. if type_ in PrimitiveToAnnotation:
  302. type_ = PrimitiveToAnnotation[type_]
  303. type_ = type_[item_type] # pyright: ignore [reportIndexIssue]
  304. if column.nullable:
  305. type_ = type_ | None
  306. return type_
  307. if name in insp.all_orm_descriptors:
  308. descriptor = insp.all_orm_descriptors[name]
  309. if hint := get_property_hint(descriptor):
  310. return hint
  311. if isinstance(descriptor, QueryableAttribute):
  312. prop = descriptor.property
  313. if isinstance(prop, Relationship):
  314. type_ = prop.mapper.class_
  315. # TODO: check for nullable?
  316. type_ = list[type_] if prop.uselist else type_ | None
  317. return type_
  318. if isinstance(attr, AssociationProxyInstance):
  319. return list[
  320. get_attribute_access_type(
  321. attr.target_class,
  322. attr.remote_attr.key, # type: ignore[attr-defined]
  323. )
  324. ]
  325. elif isinstance(cls, type) and not is_generic_alias(cls) and issubclass(cls, Model):
  326. # Check in the annotations directly (for sqlmodel.Relationship)
  327. hints = get_type_hints(cls)
  328. if name in hints:
  329. type_ = hints[name]
  330. type_origin = get_origin(type_)
  331. if isinstance(type_origin, type) and issubclass(type_origin, Mapped):
  332. return get_args(type_)[0] # SQLAlchemy v2
  333. if isinstance(type_, ModelField):
  334. return type_.type_ # SQLAlchemy v1.4
  335. return type_
  336. elif is_union(cls):
  337. # Check in each arg of the annotation.
  338. return unionize(
  339. *(get_attribute_access_type(arg, name) for arg in get_args(cls))
  340. )
  341. elif isinstance(cls, type):
  342. # Bare class
  343. exceptions = NameError
  344. try:
  345. hints = get_type_hints(cls)
  346. if name in hints:
  347. return hints[name]
  348. except exceptions as e:
  349. console.warn(f"Failed to resolve ForwardRefs for {cls}.{name} due to {e}")
  350. pass
  351. return None # Attribute is not accessible.
  352. @lru_cache
  353. def get_base_class(cls: GenericType) -> type:
  354. """Get the base class of a class.
  355. Args:
  356. cls: The class.
  357. Returns:
  358. The base class of the class.
  359. Raises:
  360. TypeError: If a literal has multiple types.
  361. """
  362. if is_literal(cls):
  363. # only literals of the same type are supported.
  364. arg_type = type(get_args(cls)[0])
  365. if not all(type(arg) is arg_type for arg in get_args(cls)):
  366. raise TypeError("only literals of the same type are supported")
  367. return type(get_args(cls)[0])
  368. if is_union(cls):
  369. return tuple(get_base_class(arg) for arg in get_args(cls)) # pyright: ignore [reportReturnType]
  370. return get_base_class(cls.__origin__) if is_generic_alias(cls) else cls
  371. def _breakpoints_satisfies_typing(cls_check: GenericType, instance: Any) -> bool:
  372. """Check if the breakpoints instance satisfies the typing.
  373. Args:
  374. cls_check: The class to check against.
  375. instance: The instance to check.
  376. Returns:
  377. Whether the breakpoints instance satisfies the typing.
  378. """
  379. cls_check_base = get_base_class(cls_check)
  380. if cls_check_base == Breakpoints:
  381. _, expected_type = get_args(cls_check)
  382. if is_literal(expected_type):
  383. for value in instance.values():
  384. if not isinstance(value, str) or value not in get_args(expected_type):
  385. return False
  386. return True
  387. elif isinstance(cls_check_base, tuple):
  388. # union type, so check all types
  389. return any(
  390. _breakpoints_satisfies_typing(type_to_check, instance)
  391. for type_to_check in get_args(cls_check)
  392. )
  393. elif cls_check_base == reflex.vars.Var and "__args__" in cls_check.__dict__:
  394. return _breakpoints_satisfies_typing(get_args(cls_check)[0], instance)
  395. return False
  396. def _issubclass(cls: GenericType, cls_check: GenericType, instance: Any = None) -> bool:
  397. """Check if a class is a subclass of another class.
  398. Args:
  399. cls: The class to check.
  400. cls_check: The class to check against.
  401. instance: An instance of cls to aid in checking generics.
  402. Returns:
  403. Whether the class is a subclass of the other class.
  404. Raises:
  405. TypeError: If the base class is not valid for issubclass.
  406. """
  407. # Special check for Any.
  408. if cls_check == Any:
  409. return True
  410. if cls in [Any, Callable, None]:
  411. return False
  412. # Get the base classes.
  413. cls_base = get_base_class(cls)
  414. cls_check_base = get_base_class(cls_check)
  415. # The class we're checking should not be a union.
  416. if isinstance(cls_base, tuple):
  417. return False
  418. # Check that fields of breakpoints match the expected values.
  419. if isinstance(instance, Breakpoints):
  420. return _breakpoints_satisfies_typing(cls_check, instance)
  421. if isinstance(cls_check_base, tuple):
  422. cls_check_base = tuple(
  423. cls_check_one if not is_typeddict(cls_check_one) else dict
  424. for cls_check_one in cls_check_base
  425. )
  426. if is_typeddict(cls_check_base):
  427. cls_check_base = dict
  428. # Check if the types match.
  429. try:
  430. return cls_check_base == Any or issubclass(cls_base, cls_check_base)
  431. except TypeError as te:
  432. # These errors typically arise from bad annotations and are hard to
  433. # debug without knowing the type that we tried to compare.
  434. raise TypeError(f"Invalid type for issubclass: {cls_base}") from te
  435. def does_obj_satisfy_typed_dict(obj: Any, cls: GenericType) -> bool:
  436. """Check if an object satisfies a typed dict.
  437. Args:
  438. obj: The object to check.
  439. cls: The typed dict to check against.
  440. Returns:
  441. Whether the object satisfies the typed dict.
  442. """
  443. if not isinstance(obj, Mapping):
  444. return False
  445. key_names_to_values = get_type_hints(cls)
  446. required_keys: frozenset[str] = getattr(cls, "__required_keys__", frozenset())
  447. if not all(
  448. isinstance(key, str)
  449. and key in key_names_to_values
  450. and _isinstance(value, key_names_to_values[key])
  451. for key, value in obj.items()
  452. ):
  453. return False
  454. # TODO in 3.14: Implement https://peps.python.org/pep-0728/ if it's approved
  455. # required keys are all present
  456. return required_keys.issubset(required_keys)
  457. def _isinstance(
  458. obj: Any,
  459. cls: GenericType,
  460. *,
  461. nested: int = 0,
  462. treat_var_as_type: bool = True,
  463. treat_mutable_obj_as_immutable: bool = False,
  464. ) -> bool:
  465. """Check if an object is an instance of a class.
  466. Args:
  467. obj: The object to check.
  468. cls: The class to check against.
  469. nested: How many levels deep to check.
  470. treat_var_as_type: Whether to treat Var as the type it represents, i.e. _var_type.
  471. 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.
  472. Returns:
  473. Whether the object is an instance of the class.
  474. """
  475. if cls is Any:
  476. return True
  477. from reflex.vars import LiteralVar, Var
  478. if cls is Var:
  479. return isinstance(obj, Var)
  480. if isinstance(obj, LiteralVar):
  481. return treat_var_as_type and _isinstance(
  482. obj._var_value, cls, nested=nested, treat_var_as_type=True
  483. )
  484. if isinstance(obj, Var):
  485. return treat_var_as_type and typehint_issubclass(
  486. obj._var_type,
  487. cls,
  488. treat_mutable_superclasss_as_immutable=treat_mutable_obj_as_immutable,
  489. treat_literals_as_union_of_types=True,
  490. treat_any_as_subtype_of_everything=True,
  491. )
  492. if cls is None or cls is type(None):
  493. return obj is None
  494. if cls is not None and is_union(cls):
  495. return any(
  496. _isinstance(obj, arg, nested=nested, treat_var_as_type=treat_var_as_type)
  497. for arg in get_args(cls)
  498. )
  499. if is_literal(cls):
  500. return obj in get_args(cls)
  501. origin = get_origin(cls)
  502. if origin is None:
  503. # cls is a typed dict
  504. if is_typeddict(cls):
  505. if nested:
  506. return does_obj_satisfy_typed_dict(obj, cls)
  507. return isinstance(obj, dict)
  508. # cls is a float
  509. if cls is float:
  510. return isinstance(obj, (float, int))
  511. # cls is a simple class
  512. return isinstance(obj, cls)
  513. args = get_args(cls)
  514. if not args:
  515. if treat_mutable_obj_as_immutable:
  516. if origin is dict:
  517. origin = Mapping
  518. elif origin is list or origin is set:
  519. origin = Sequence
  520. # cls is a simple generic class
  521. return isinstance(obj, origin)
  522. if origin is Var and args:
  523. # cls is a Var
  524. return _isinstance(
  525. obj,
  526. args[0],
  527. nested=nested,
  528. treat_var_as_type=treat_var_as_type,
  529. treat_mutable_obj_as_immutable=treat_mutable_obj_as_immutable,
  530. )
  531. if nested > 0 and args:
  532. if origin is list:
  533. expected_class = Sequence if treat_mutable_obj_as_immutable else list
  534. return isinstance(obj, expected_class) and all(
  535. _isinstance(
  536. item,
  537. args[0],
  538. nested=nested - 1,
  539. treat_var_as_type=treat_var_as_type,
  540. )
  541. for item in obj
  542. )
  543. if origin is tuple:
  544. if args[-1] is Ellipsis:
  545. return isinstance(obj, tuple) and all(
  546. _isinstance(
  547. item,
  548. args[0],
  549. nested=nested - 1,
  550. treat_var_as_type=treat_var_as_type,
  551. )
  552. for item in obj
  553. )
  554. return (
  555. isinstance(obj, tuple)
  556. and len(obj) == len(args)
  557. and all(
  558. _isinstance(
  559. item,
  560. arg,
  561. nested=nested - 1,
  562. treat_var_as_type=treat_var_as_type,
  563. )
  564. for item, arg in zip(obj, args, strict=True)
  565. )
  566. )
  567. if origin in (dict, Mapping, Breakpoints):
  568. expected_class = (
  569. dict
  570. if origin is dict and not treat_mutable_obj_as_immutable
  571. else Mapping
  572. )
  573. return isinstance(obj, expected_class) and all(
  574. _isinstance(
  575. key, args[0], nested=nested - 1, treat_var_as_type=treat_var_as_type
  576. )
  577. and _isinstance(
  578. value,
  579. args[1],
  580. nested=nested - 1,
  581. treat_var_as_type=treat_var_as_type,
  582. )
  583. for key, value in obj.items()
  584. )
  585. if origin is set:
  586. expected_class = Sequence if treat_mutable_obj_as_immutable else set
  587. return isinstance(obj, expected_class) and all(
  588. _isinstance(
  589. item,
  590. args[0],
  591. nested=nested - 1,
  592. treat_var_as_type=treat_var_as_type,
  593. )
  594. for item in obj
  595. )
  596. if args:
  597. from reflex.vars import Field
  598. if origin is Field:
  599. return _isinstance(
  600. obj, args[0], nested=nested, treat_var_as_type=treat_var_as_type
  601. )
  602. return isinstance(obj, get_base_class(cls))
  603. def is_dataframe(value: type) -> bool:
  604. """Check if the given value is a dataframe.
  605. Args:
  606. value: The value to check.
  607. Returns:
  608. Whether the value is a dataframe.
  609. """
  610. if is_generic_alias(value) or value == Any:
  611. return False
  612. return value.__name__ == "DataFrame"
  613. def is_valid_var_type(type_: type) -> bool:
  614. """Check if the given type is a valid prop type.
  615. Args:
  616. type_: The type to check.
  617. Returns:
  618. Whether the type is a valid prop type.
  619. """
  620. from reflex.utils import serializers
  621. if is_union(type_):
  622. return all(is_valid_var_type(arg) for arg in get_args(type_))
  623. return (
  624. _issubclass(type_, StateVar)
  625. or serializers.has_serializer(type_)
  626. or dataclasses.is_dataclass(type_)
  627. )
  628. def is_backend_base_variable(name: str, cls: type) -> bool:
  629. """Check if this variable name correspond to a backend variable.
  630. Args:
  631. name: The name of the variable to check
  632. cls: The class of the variable to check
  633. Returns:
  634. bool: The result of the check
  635. """
  636. if name in RESERVED_BACKEND_VAR_NAMES:
  637. return False
  638. if not name.startswith("_"):
  639. return False
  640. if name.startswith("__"):
  641. return False
  642. if name.startswith(f"_{cls.__name__}__"):
  643. return False
  644. # Extract the namespace of the original module if defined (dynamic substates).
  645. if callable(getattr(cls, "_get_type_hints", None)):
  646. hints = cls._get_type_hints()
  647. else:
  648. hints = get_type_hints(cls)
  649. if name in hints:
  650. hint = get_origin(hints[name])
  651. if hint == ClassVar:
  652. return False
  653. if name in cls.inherited_backend_vars:
  654. return False
  655. from reflex.vars.base import is_computed_var
  656. if name in cls.__dict__:
  657. value = cls.__dict__[name]
  658. if type(value) is classmethod:
  659. return False
  660. if callable(value):
  661. return False
  662. if isinstance(
  663. value,
  664. (
  665. types.FunctionType,
  666. property,
  667. cached_property,
  668. ),
  669. ) or is_computed_var(value):
  670. return False
  671. return True
  672. def check_type_in_allowed_types(value_type: type, allowed_types: Iterable) -> bool:
  673. """Check that a value type is found in a list of allowed types.
  674. Args:
  675. value_type: Type of value.
  676. allowed_types: Iterable of allowed types.
  677. Returns:
  678. If the type is found in the allowed types.
  679. """
  680. return get_base_class(value_type) in allowed_types
  681. def check_prop_in_allowed_types(prop: Any, allowed_types: Iterable) -> bool:
  682. """Check that a prop value is in a list of allowed types.
  683. Does the check in a way that works regardless if it's a raw value or a state Var.
  684. Args:
  685. prop: The prop to check.
  686. allowed_types: The list of allowed types.
  687. Returns:
  688. If the prop type match one of the allowed_types.
  689. """
  690. from reflex.vars import Var
  691. type_ = prop._var_type if isinstance(prop, Var) else type(prop)
  692. return type_ in allowed_types
  693. def is_encoded_fstring(value: Any) -> bool:
  694. """Check if a value is an encoded Var f-string.
  695. Args:
  696. value: The value string to check.
  697. Returns:
  698. Whether the value is an f-string
  699. """
  700. return isinstance(value, str) and constants.REFLEX_VAR_OPENING_TAG in value
  701. def validate_literal(key: str, value: Any, expected_type: type, comp_name: str):
  702. """Check that a value is a valid literal.
  703. Args:
  704. key: The prop name.
  705. value: The prop value to validate.
  706. expected_type: The expected type(literal type).
  707. comp_name: Name of the component.
  708. Raises:
  709. ValueError: When the value is not a valid literal.
  710. """
  711. from reflex.vars import Var
  712. if (
  713. is_literal(expected_type)
  714. and not isinstance(value, Var) # validating vars is not supported yet.
  715. and not is_encoded_fstring(value) # f-strings are not supported.
  716. and value not in expected_type.__args__
  717. ):
  718. allowed_values = expected_type.__args__
  719. if value not in allowed_values:
  720. allowed_value_str = ",".join(
  721. [str(v) if not isinstance(v, str) else f"'{v}'" for v in allowed_values]
  722. )
  723. value_str = f"'{value}'" if isinstance(value, str) else value
  724. raise ValueError(
  725. f"prop value for {key!s} of the `{comp_name}` component should be one of the following: {allowed_value_str}. Got {value_str} instead"
  726. )
  727. def validate_parameter_literals(func: Callable):
  728. """Decorator to check that the arguments passed to a function
  729. correspond to the correct function parameter if it (the parameter)
  730. is a literal type.
  731. Args:
  732. func: The function to validate.
  733. Returns:
  734. The wrapper function.
  735. """
  736. console.deprecate(
  737. "validate_parameter_literals",
  738. reason="Use manual validation instead.",
  739. deprecation_version="0.7.11",
  740. removal_version="0.8.0",
  741. dedupe=True,
  742. )
  743. func_params = list(inspect.signature(func).parameters.items())
  744. annotations = {param[0]: param[1].annotation for param in func_params}
  745. @wraps(func)
  746. def wrapper(*args, **kwargs):
  747. # validate args
  748. for param, arg in zip(annotations, args, strict=False):
  749. if annotations[param] is inspect.Parameter.empty:
  750. continue
  751. validate_literal(param, arg, annotations[param], func.__name__)
  752. # validate kwargs.
  753. for key, value in kwargs.items():
  754. annotation = annotations.get(key)
  755. if not annotation or annotation is inspect.Parameter.empty:
  756. continue
  757. validate_literal(key, value, annotation, func.__name__)
  758. return func(*args, **kwargs)
  759. return wrapper
  760. # Store this here for performance.
  761. StateBases = get_base_class(StateVar)
  762. StateIterBases = get_base_class(StateIterVar)
  763. def safe_issubclass(cls: Any, cls_check: Any | tuple[Any, ...]):
  764. """Check if a class is a subclass of another class. Returns False if internal error occurs.
  765. Args:
  766. cls: The class to check.
  767. cls_check: The class to check against.
  768. Returns:
  769. Whether the class is a subclass of the other class.
  770. """
  771. try:
  772. return issubclass(cls, cls_check)
  773. except TypeError:
  774. return False
  775. def typehint_issubclass(
  776. possible_subclass: Any,
  777. possible_superclass: Any,
  778. *,
  779. treat_mutable_superclasss_as_immutable: bool = False,
  780. treat_literals_as_union_of_types: bool = True,
  781. treat_any_as_subtype_of_everything: bool = False,
  782. ) -> bool:
  783. """Check if a type hint is a subclass of another type hint.
  784. Args:
  785. possible_subclass: The type hint to check.
  786. possible_superclass: The type hint to check against.
  787. treat_mutable_superclasss_as_immutable: Whether to treat target classes as immutable.
  788. treat_literals_as_union_of_types: Whether to treat literals as a union of their types.
  789. treat_any_as_subtype_of_everything: Whether to treat Any as a subtype of everything. This is the default behavior in Python.
  790. Returns:
  791. Whether the type hint is a subclass of the other type hint.
  792. """
  793. if possible_superclass is Any:
  794. return True
  795. if possible_subclass is Any:
  796. return treat_any_as_subtype_of_everything
  797. if possible_subclass is NoReturn:
  798. return True
  799. provided_type_origin = get_origin(possible_subclass)
  800. accepted_type_origin = get_origin(possible_superclass)
  801. if provided_type_origin is None and accepted_type_origin is None:
  802. # In this case, we are dealing with a non-generic type, so we can use issubclass
  803. return issubclass(possible_subclass, possible_superclass)
  804. if treat_literals_as_union_of_types and is_literal(possible_superclass):
  805. args = get_args(possible_superclass)
  806. return any(
  807. typehint_issubclass(
  808. possible_subclass,
  809. type(arg),
  810. treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable,
  811. treat_literals_as_union_of_types=treat_literals_as_union_of_types,
  812. treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything,
  813. )
  814. for arg in args
  815. )
  816. if is_literal(possible_subclass):
  817. args = get_args(possible_subclass)
  818. return all(
  819. _isinstance(
  820. arg,
  821. possible_superclass,
  822. treat_mutable_obj_as_immutable=treat_mutable_superclasss_as_immutable,
  823. nested=2,
  824. )
  825. for arg in args
  826. )
  827. provided_type_origin = (
  828. Union if provided_type_origin is types.UnionType else provided_type_origin
  829. )
  830. accepted_type_origin = (
  831. Union if accepted_type_origin is types.UnionType else accepted_type_origin
  832. )
  833. # Get type arguments (e.g., [float, int] for dict[float, int])
  834. provided_args = get_args(possible_subclass)
  835. accepted_args = get_args(possible_superclass)
  836. if accepted_type_origin is Union:
  837. if provided_type_origin is not Union:
  838. return any(
  839. typehint_issubclass(
  840. possible_subclass,
  841. accepted_arg,
  842. treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable,
  843. treat_literals_as_union_of_types=treat_literals_as_union_of_types,
  844. treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything,
  845. )
  846. for accepted_arg in accepted_args
  847. )
  848. return all(
  849. any(
  850. typehint_issubclass(
  851. provided_arg,
  852. accepted_arg,
  853. treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable,
  854. treat_literals_as_union_of_types=treat_literals_as_union_of_types,
  855. treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything,
  856. )
  857. for accepted_arg in accepted_args
  858. )
  859. for provided_arg in provided_args
  860. )
  861. if provided_type_origin is Union:
  862. return all(
  863. typehint_issubclass(
  864. provided_arg,
  865. possible_superclass,
  866. treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable,
  867. treat_literals_as_union_of_types=treat_literals_as_union_of_types,
  868. treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything,
  869. )
  870. for provided_arg in provided_args
  871. )
  872. provided_type_origin = provided_type_origin or possible_subclass
  873. accepted_type_origin = accepted_type_origin or possible_superclass
  874. if treat_mutable_superclasss_as_immutable:
  875. if accepted_type_origin is dict:
  876. accepted_type_origin = Mapping
  877. elif accepted_type_origin is list or accepted_type_origin is set:
  878. accepted_type_origin = Sequence
  879. # Check if the origin of both types is the same (e.g., list for list[int])
  880. if not safe_issubclass(
  881. provided_type_origin or possible_subclass,
  882. accepted_type_origin or possible_superclass,
  883. ):
  884. return False
  885. # Ensure all specific types are compatible with accepted types
  886. # Note this is not necessarily correct, as it doesn't check against contravariance and covariance
  887. # It also ignores when the length of the arguments is different
  888. return all(
  889. typehint_issubclass(
  890. provided_arg,
  891. accepted_arg,
  892. treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable,
  893. treat_literals_as_union_of_types=treat_literals_as_union_of_types,
  894. treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything,
  895. )
  896. for provided_arg, accepted_arg in zip(
  897. provided_args, accepted_args, strict=False
  898. )
  899. if accepted_arg is not Any
  900. )