serializers.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. """Serializers used to convert Var types to JSON strings."""
  2. from __future__ import annotations
  3. import json
  4. import types as builtin_types
  5. import warnings
  6. from datetime import date, datetime, time, timedelta
  7. from typing import Any, Callable, Dict, List, Set, Tuple, Type, Union, get_type_hints
  8. from reflex.base import Base
  9. from reflex.constants.colors import Color, format_color
  10. from reflex.utils import exceptions, format, types
  11. # Mapping from type to a serializer.
  12. # The serializer should convert the type to a JSON object.
  13. SerializedType = Union[str, bool, int, float, list, dict]
  14. Serializer = Callable[[Type], SerializedType]
  15. SERIALIZERS: dict[Type, Serializer] = {}
  16. def serializer(fn: Serializer) -> Serializer:
  17. """Decorator to add a serializer for a given type.
  18. Args:
  19. fn: The function to decorate.
  20. Returns:
  21. The decorated function.
  22. Raises:
  23. ValueError: If the function does not take a single argument.
  24. """
  25. # Get the global serializers.
  26. global SERIALIZERS
  27. # Check the type hints to get the type of the argument.
  28. type_hints = get_type_hints(fn)
  29. args = [arg for arg in type_hints if arg != "return"]
  30. # Make sure the function takes a single argument.
  31. if len(args) != 1:
  32. raise ValueError("Serializer must take a single argument.")
  33. # Get the type of the argument.
  34. type_ = type_hints[args[0]]
  35. # Make sure the type is not already registered.
  36. registered_fn = SERIALIZERS.get(type_)
  37. if registered_fn is not None and registered_fn != fn:
  38. raise ValueError(
  39. f"Serializer for type {type_} is already registered as {registered_fn.__qualname__}."
  40. )
  41. # Register the serializer.
  42. SERIALIZERS[type_] = fn
  43. # Return the function.
  44. return fn
  45. def serialize(value: Any) -> SerializedType | None:
  46. """Serialize the value to a JSON string.
  47. Args:
  48. value: The value to serialize.
  49. Returns:
  50. The serialized value, or None if a serializer is not found.
  51. """
  52. # Get the serializer for the type.
  53. serializer = get_serializer(type(value))
  54. # If there is no serializer, return None.
  55. if serializer is None:
  56. return None
  57. # Serialize the value.
  58. return serializer(value)
  59. def get_serializer(type_: Type) -> Serializer | None:
  60. """Get the serializer for the type.
  61. Args:
  62. type_: The type to get the serializer for.
  63. Returns:
  64. The serializer for the type, or None if there is no serializer.
  65. """
  66. global SERIALIZERS
  67. # First, check if the type is registered.
  68. serializer = SERIALIZERS.get(type_)
  69. if serializer is not None:
  70. return serializer
  71. # If the type is not registered, check if it is a subclass of a registered type.
  72. for registered_type, serializer in reversed(SERIALIZERS.items()):
  73. if types._issubclass(type_, registered_type):
  74. return serializer
  75. # If there is no serializer, return None.
  76. return None
  77. def has_serializer(type_: Type) -> bool:
  78. """Check if there is a serializer for the type.
  79. Args:
  80. type_: The type to check.
  81. Returns:
  82. Whether there is a serializer for the type.
  83. """
  84. return get_serializer(type_) is not None
  85. @serializer
  86. def serialize_type(value: type) -> str:
  87. """Serialize a python type.
  88. Args:
  89. value: the type to serialize.
  90. Returns:
  91. The serialized type.
  92. """
  93. return value.__name__
  94. @serializer
  95. def serialize_str(value: str) -> str:
  96. """Serialize a string.
  97. Args:
  98. value: The string to serialize.
  99. Returns:
  100. The serialized string.
  101. """
  102. return value
  103. @serializer
  104. def serialize_primitive(value: Union[bool, int, float, None]) -> str:
  105. """Serialize a primitive type.
  106. Args:
  107. value: The number/bool/None to serialize.
  108. Returns:
  109. The serialized number/bool/None.
  110. """
  111. return format.json_dumps(value)
  112. @serializer
  113. def serialize_base(value: Base) -> str:
  114. """Serialize a Base instance.
  115. Args:
  116. value : The Base to serialize.
  117. Returns:
  118. The serialized Base.
  119. """
  120. return value.json()
  121. @serializer
  122. def serialize_list(value: Union[List, Tuple, Set]) -> str:
  123. """Serialize a list to a JSON string.
  124. Args:
  125. value: The list to serialize.
  126. Returns:
  127. The serialized list.
  128. """
  129. # Dump the list to a string.
  130. fprop = format.json_dumps(list(value))
  131. # Unwrap var values.
  132. return format.unwrap_vars(fprop)
  133. @serializer
  134. def serialize_dict(prop: Dict[str, Any]) -> str:
  135. """Serialize a dictionary to a JSON string.
  136. Args:
  137. prop: The dictionary to serialize.
  138. Returns:
  139. The serialized dictionary.
  140. Raises:
  141. InvalidStylePropError: If the style prop is invalid.
  142. """
  143. # Import here to avoid circular imports.
  144. from reflex.event import EventHandler
  145. prop_dict = {}
  146. for key, value in prop.items():
  147. if types._issubclass(type(value), Callable):
  148. raise exceptions.InvalidStylePropError(
  149. f"The style prop `{format.to_snake_case(key)}` cannot have " # type: ignore
  150. f"`{value.fn.__qualname__ if isinstance(value, EventHandler) else value.__qualname__ if isinstance(value, builtin_types.FunctionType) else value}`, "
  151. f"an event handler or callable as its value"
  152. )
  153. prop_dict[key] = value
  154. # Dump the dict to a string.
  155. fprop = format.json_dumps(prop_dict)
  156. # Unwrap var values.
  157. return format.unwrap_vars(fprop)
  158. @serializer
  159. def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
  160. """Serialize a datetime to a JSON string.
  161. Args:
  162. dt: The datetime to serialize.
  163. Returns:
  164. The serialized datetime.
  165. """
  166. return str(dt)
  167. @serializer
  168. def serialize_color(color: Color) -> str:
  169. """Serialize a color.
  170. Args:
  171. color: The color to serialize.
  172. Returns:
  173. The serialized color.
  174. """
  175. return format_color(color.color, color.shade, color.alpha)
  176. try:
  177. from pandas import DataFrame
  178. def format_dataframe_values(df: DataFrame) -> List[List[Any]]:
  179. """Format dataframe values to a list of lists.
  180. Args:
  181. df: The dataframe to format.
  182. Returns:
  183. The dataframe as a list of lists.
  184. """
  185. return [
  186. [str(d) if isinstance(d, (list, tuple)) else d for d in data]
  187. for data in list(df.values.tolist())
  188. ]
  189. @serializer
  190. def serialize_dataframe(df: DataFrame) -> dict:
  191. """Serialize a pandas dataframe.
  192. Args:
  193. df: The dataframe to serialize.
  194. Returns:
  195. The serialized dataframe.
  196. """
  197. return {
  198. "columns": df.columns.tolist(),
  199. "data": format_dataframe_values(df),
  200. }
  201. except ImportError:
  202. pass
  203. try:
  204. from plotly.graph_objects import Figure
  205. from plotly.io import to_json
  206. @serializer
  207. def serialize_figure(figure: Figure) -> list:
  208. """Serialize a plotly figure.
  209. Args:
  210. figure: The figure to serialize.
  211. Returns:
  212. The serialized figure.
  213. """
  214. return json.loads(str(to_json(figure)))["data"]
  215. except ImportError:
  216. pass
  217. try:
  218. import base64
  219. import io
  220. from PIL.Image import MIME
  221. from PIL.Image import Image as Img
  222. @serializer
  223. def serialize_image(image: Img) -> str:
  224. """Serialize a plotly figure.
  225. Args:
  226. image: The image to serialize.
  227. Returns:
  228. The serialized image.
  229. """
  230. buff = io.BytesIO()
  231. image_format = getattr(image, "format", None) or "PNG"
  232. image.save(buff, format=image_format)
  233. image_bytes = buff.getvalue()
  234. base64_image = base64.b64encode(image_bytes).decode("utf-8")
  235. try:
  236. # Newer method to get the mime type, but does not always work.
  237. mime_type = image.get_format_mimetype() # type: ignore
  238. except AttributeError:
  239. try:
  240. # Fallback method
  241. mime_type = MIME[image_format]
  242. except KeyError:
  243. # Unknown mime_type: warn and return image/png and hope the browser can sort it out.
  244. warnings.warn(
  245. f"Unknown mime type for {image} {image_format}. Defaulting to image/png"
  246. )
  247. mime_type = "image/png"
  248. return f"data:{mime_type};base64,{base64_image}"
  249. except ImportError:
  250. pass