types.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. """Contains custom types and methods to check types."""
  2. from __future__ import annotations
  3. import contextlib
  4. import typing
  5. from datetime import date, datetime, time, timedelta
  6. from typing import Any, Callable, Type, Union, _GenericAlias # type: ignore
  7. from reflex.base import Base
  8. # Union of generic types.
  9. GenericType = Union[Type, _GenericAlias]
  10. # Valid state var types.
  11. PrimitiveType = Union[int, float, bool, str, list, dict, set, tuple]
  12. StateVar = Union[PrimitiveType, Base, None]
  13. StateIterVar = Union[list, set, tuple]
  14. def get_args(alias: _GenericAlias) -> tuple[Type, ...]:
  15. """Get the arguments of a type alias.
  16. Args:
  17. alias: The type alias.
  18. Returns:
  19. The arguments of the type alias.
  20. """
  21. return alias.__args__
  22. def is_generic_alias(cls: GenericType) -> bool:
  23. """Check whether the class is a generic alias.
  24. Args:
  25. cls: The class to check.
  26. Returns:
  27. Whether the class is a generic alias.
  28. """
  29. # For older versions of Python.
  30. if isinstance(cls, _GenericAlias):
  31. return True
  32. with contextlib.suppress(ImportError):
  33. from typing import _SpecialGenericAlias # type: ignore
  34. if isinstance(cls, _SpecialGenericAlias):
  35. return True
  36. # For newer versions of Python.
  37. try:
  38. from types import GenericAlias # type: ignore
  39. return isinstance(cls, GenericAlias)
  40. except ImportError:
  41. return False
  42. def is_union(cls: GenericType) -> bool:
  43. """Check if a class is a Union.
  44. Args:
  45. cls: The class to check.
  46. Returns:
  47. Whether the class is a Union.
  48. """
  49. with contextlib.suppress(ImportError):
  50. from typing import _UnionGenericAlias # type: ignore
  51. return isinstance(cls, _UnionGenericAlias)
  52. return cls.__origin__ == Union if is_generic_alias(cls) else False
  53. def get_base_class(cls: GenericType) -> Type:
  54. """Get the base class of a class.
  55. Args:
  56. cls: The class.
  57. Returns:
  58. The base class of the class.
  59. """
  60. if is_union(cls):
  61. return tuple(get_base_class(arg) for arg in get_args(cls))
  62. return get_base_class(cls.__origin__) if is_generic_alias(cls) else cls
  63. def _issubclass(cls: GenericType, cls_check: GenericType) -> bool:
  64. """Check if a class is a subclass of another class.
  65. Args:
  66. cls: The class to check.
  67. cls_check: The class to check against.
  68. Returns:
  69. Whether the class is a subclass of the other class.
  70. """
  71. # Special check for Any.
  72. if cls_check == Any:
  73. return True
  74. if cls in [Any, Callable]:
  75. return False
  76. # Get the base classes.
  77. cls_base = get_base_class(cls)
  78. cls_check_base = get_base_class(cls_check)
  79. # The class we're checking should not be a union.
  80. if isinstance(cls_base, tuple):
  81. return False
  82. # Check if the types match.
  83. return cls_check_base == Any or issubclass(cls_base, cls_check_base)
  84. def _isinstance(obj: Any, cls: GenericType) -> bool:
  85. """Check if an object is an instance of a class.
  86. Args:
  87. obj: The object to check.
  88. cls: The class to check against.
  89. Returns:
  90. Whether the object is an instance of the class.
  91. """
  92. return isinstance(obj, get_base_class(cls))
  93. def is_dataframe(value: Type) -> bool:
  94. """Check if the given value is a dataframe.
  95. Args:
  96. value: The value to check.
  97. Returns:
  98. Whether the value is a dataframe.
  99. """
  100. if is_generic_alias(value) or value == typing.Any:
  101. return False
  102. return value.__name__ == "DataFrame"
  103. def is_image(value: Type) -> bool:
  104. """Check if the given value is a pillow image. By checking if the value subclasses PIL.
  105. Args:
  106. value: The value to check.
  107. Returns:
  108. Whether the value is a pillow image.
  109. """
  110. if is_generic_alias(value) or value == typing.Any:
  111. return False
  112. return "PIL" in value.__module__
  113. def is_figure(value: Type) -> bool:
  114. """Check if the given value is a figure.
  115. Args:
  116. value: The value to check.
  117. Returns:
  118. Whether the value is a figure.
  119. """
  120. return value.__name__ == "Figure"
  121. def is_datetime(value: Type) -> bool:
  122. """Check if the given value is a datetime object.
  123. Args:
  124. value: The value to check.
  125. Returns:
  126. Whether the value is a date, datetime, time, or timedelta.
  127. """
  128. return issubclass(value, (date, datetime, time, timedelta))
  129. def is_valid_var_type(var: Type) -> bool:
  130. """Check if the given value is a valid prop type.
  131. Args:
  132. var: The value to check.
  133. Returns:
  134. Whether the value is a valid prop type.
  135. """
  136. return (
  137. _issubclass(var, StateVar)
  138. or is_dataframe(var)
  139. or is_figure(var)
  140. or is_image(var)
  141. or is_datetime(var)
  142. )
  143. def is_backend_variable(name: str) -> bool:
  144. """Check if this variable name correspond to a backend variable.
  145. Args:
  146. name: The name of the variable to check
  147. Returns:
  148. bool: The result of the check
  149. """
  150. return name.startswith("_") and not name.startswith("__")
  151. # Store this here for performance.
  152. StateBases = get_base_class(StateVar)
  153. StateIterBases = get_base_class(StateIterVar)