serializers.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. """Serializers used to convert Var types to JSON strings."""
  2. from __future__ import annotations
  3. import functools
  4. import json
  5. import types as builtin_types
  6. import warnings
  7. from datetime import date, datetime, time, timedelta
  8. from enum import Enum
  9. from pathlib import Path
  10. from typing import (
  11. Any,
  12. Callable,
  13. Dict,
  14. List,
  15. Literal,
  16. Optional,
  17. Set,
  18. Tuple,
  19. Type,
  20. Union,
  21. get_type_hints,
  22. overload,
  23. )
  24. from reflex.base import Base
  25. from reflex.constants.colors import Color, format_color
  26. from reflex.utils import exceptions, types
  27. # Mapping from type to a serializer.
  28. # The serializer should convert the type to a JSON object.
  29. SerializedType = Union[str, bool, int, float, list, dict]
  30. Serializer = Callable[[Type], SerializedType]
  31. SERIALIZERS: dict[Type, Serializer] = {}
  32. SERIALIZER_TYPES: dict[Type, Type] = {}
  33. def serializer(
  34. fn: Serializer | None = None,
  35. to: Type | None = None,
  36. ) -> Serializer:
  37. """Decorator to add a serializer for a given type.
  38. Args:
  39. fn: The function to decorate.
  40. to: The type returned by the serializer. If this is `str`, then any Var created from this type will be treated as a string.
  41. Returns:
  42. The decorated function.
  43. Raises:
  44. ValueError: If the function does not take a single argument.
  45. """
  46. if fn is None:
  47. # If the function is not provided, return a partial that acts as a decorator.
  48. return functools.partial(serializer, to=to) # type: ignore
  49. # Check the type hints to get the type of the argument.
  50. type_hints = get_type_hints(fn)
  51. args = [arg for arg in type_hints if arg != "return"]
  52. # Make sure the function takes a single argument.
  53. if len(args) != 1:
  54. raise ValueError("Serializer must take a single argument.")
  55. # Get the type of the argument.
  56. type_ = type_hints[args[0]]
  57. # Make sure the type is not already registered.
  58. registered_fn = SERIALIZERS.get(type_)
  59. if registered_fn is not None and registered_fn != fn:
  60. raise ValueError(
  61. f"Serializer for type {type_} is already registered as {registered_fn.__qualname__}."
  62. )
  63. # Apply type transformation if requested
  64. if to is not None:
  65. SERIALIZER_TYPES[type_] = to
  66. get_serializer_type.cache_clear()
  67. # Register the serializer.
  68. SERIALIZERS[type_] = fn
  69. get_serializer.cache_clear()
  70. # Return the function.
  71. return fn
  72. @overload
  73. def serialize(
  74. value: Any, get_type: Literal[True]
  75. ) -> Tuple[Optional[SerializedType], Optional[types.GenericType]]: ...
  76. @overload
  77. def serialize(value: Any, get_type: Literal[False]) -> Optional[SerializedType]: ...
  78. @overload
  79. def serialize(value: Any) -> Optional[SerializedType]: ...
  80. def serialize(
  81. value: Any, get_type: bool = False
  82. ) -> Union[
  83. Optional[SerializedType],
  84. Tuple[Optional[SerializedType], Optional[types.GenericType]],
  85. ]:
  86. """Serialize the value to a JSON string.
  87. Args:
  88. value: The value to serialize.
  89. get_type: Whether to return the type of the serialized value.
  90. Returns:
  91. The serialized value, or None if a serializer is not found.
  92. """
  93. # Get the serializer for the type.
  94. serializer = get_serializer(type(value))
  95. # If there is no serializer, return None.
  96. if serializer is None:
  97. if get_type:
  98. return None, None
  99. return None
  100. # Serialize the value.
  101. serialized = serializer(value)
  102. # Return the serialized value and the type.
  103. if get_type:
  104. return serialized, get_serializer_type(type(value))
  105. else:
  106. return serialized
  107. @functools.lru_cache
  108. def get_serializer(type_: Type) -> Optional[Serializer]:
  109. """Get the serializer for the type.
  110. Args:
  111. type_: The type to get the serializer for.
  112. Returns:
  113. The serializer for the type, or None if there is no serializer.
  114. """
  115. # First, check if the type is registered.
  116. serializer = SERIALIZERS.get(type_)
  117. if serializer is not None:
  118. return serializer
  119. # If the type is not registered, check if it is a subclass of a registered type.
  120. for registered_type, serializer in reversed(SERIALIZERS.items()):
  121. if types._issubclass(type_, registered_type):
  122. return serializer
  123. # If there is no serializer, return None.
  124. return None
  125. @functools.lru_cache
  126. def get_serializer_type(type_: Type) -> Optional[Type]:
  127. """Get the converted type for the type after serializing.
  128. Args:
  129. type_: The type to get the serializer type for.
  130. Returns:
  131. The serialized type for the type, or None if there is no type conversion registered.
  132. """
  133. # First, check if the type is registered.
  134. serializer = SERIALIZER_TYPES.get(type_)
  135. if serializer is not None:
  136. return serializer
  137. # If the type is not registered, check if it is a subclass of a registered type.
  138. for registered_type, serializer in reversed(SERIALIZER_TYPES.items()):
  139. if types._issubclass(type_, registered_type):
  140. return serializer
  141. # If there is no serializer, return None.
  142. return None
  143. def has_serializer(type_: Type) -> bool:
  144. """Check if there is a serializer for the type.
  145. Args:
  146. type_: The type to check.
  147. Returns:
  148. Whether there is a serializer for the type.
  149. """
  150. return get_serializer(type_) is not None
  151. @serializer(to=str)
  152. def serialize_type(value: type) -> str:
  153. """Serialize a python type.
  154. Args:
  155. value: the type to serialize.
  156. Returns:
  157. The serialized type.
  158. """
  159. return value.__name__
  160. @serializer
  161. def serialize_str(value: str) -> str:
  162. """Serialize a string.
  163. Args:
  164. value: The string to serialize.
  165. Returns:
  166. The serialized string.
  167. """
  168. return value
  169. @serializer
  170. def serialize_primitive(value: Union[bool, int, float, None]) -> str:
  171. """Serialize a primitive type.
  172. Args:
  173. value: The number/bool/None to serialize.
  174. Returns:
  175. The serialized number/bool/None.
  176. """
  177. from reflex.utils import format
  178. return format.json_dumps(value)
  179. @serializer
  180. def serialize_base(value: Base) -> str:
  181. """Serialize a Base instance.
  182. Args:
  183. value : The Base to serialize.
  184. Returns:
  185. The serialized Base.
  186. """
  187. return value.json()
  188. @serializer
  189. def serialize_list(value: Union[List, Tuple, Set]) -> str:
  190. """Serialize a list to a JSON string.
  191. Args:
  192. value: The list to serialize.
  193. Returns:
  194. The serialized list.
  195. """
  196. from reflex.utils import format
  197. # Dump the list to a string.
  198. fprop = format.json_dumps(list(value))
  199. # Unwrap var values.
  200. return format.unwrap_vars(fprop)
  201. @serializer
  202. def serialize_dict(prop: Dict[str, Any]) -> str:
  203. """Serialize a dictionary to a JSON string.
  204. Args:
  205. prop: The dictionary to serialize.
  206. Returns:
  207. The serialized dictionary.
  208. Raises:
  209. InvalidStylePropError: If the style prop is invalid.
  210. """
  211. # Import here to avoid circular imports.
  212. from reflex.event import EventHandler
  213. from reflex.utils import format
  214. prop_dict = {}
  215. for key, value in prop.items():
  216. if types._issubclass(type(value), Callable):
  217. raise exceptions.InvalidStylePropError(
  218. f"The style prop `{format.to_snake_case(key)}` cannot have " # type: ignore
  219. f"`{value.fn.__qualname__ if isinstance(value, EventHandler) else value.__qualname__ if isinstance(value, builtin_types.FunctionType) else value}`, "
  220. f"an event handler or callable as its value"
  221. )
  222. prop_dict[key] = value
  223. # Dump the dict to a string.
  224. fprop = format.json_dumps(prop_dict)
  225. # Unwrap var values.
  226. return format.unwrap_vars(fprop)
  227. @serializer(to=str)
  228. def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
  229. """Serialize a datetime to a JSON string.
  230. Args:
  231. dt: The datetime to serialize.
  232. Returns:
  233. The serialized datetime.
  234. """
  235. return str(dt)
  236. @serializer(to=str)
  237. def serialize_path(path: Path) -> str:
  238. """Serialize a pathlib.Path to a JSON string.
  239. Args:
  240. path: The path to serialize.
  241. Returns:
  242. The serialized path.
  243. """
  244. return str(path.as_posix())
  245. @serializer
  246. def serialize_enum(en: Enum) -> str:
  247. """Serialize a enum to a JSON string.
  248. Args:
  249. en: The enum to serialize.
  250. Returns:
  251. The serialized enum.
  252. """
  253. return en.value
  254. @serializer(to=str)
  255. def serialize_color(color: Color) -> str:
  256. """Serialize a color.
  257. Args:
  258. color: The color to serialize.
  259. Returns:
  260. The serialized color.
  261. """
  262. return format_color(color.color, color.shade, color.alpha)
  263. try:
  264. from pandas import DataFrame
  265. def format_dataframe_values(df: DataFrame) -> List[List[Any]]:
  266. """Format dataframe values to a list of lists.
  267. Args:
  268. df: The dataframe to format.
  269. Returns:
  270. The dataframe as a list of lists.
  271. """
  272. return [
  273. [str(d) if isinstance(d, (list, tuple)) else d for d in data]
  274. for data in list(df.values.tolist())
  275. ]
  276. @serializer
  277. def serialize_dataframe(df: DataFrame) -> dict:
  278. """Serialize a pandas dataframe.
  279. Args:
  280. df: The dataframe to serialize.
  281. Returns:
  282. The serialized dataframe.
  283. """
  284. return {
  285. "columns": df.columns.tolist(),
  286. "data": format_dataframe_values(df),
  287. }
  288. except ImportError:
  289. pass
  290. try:
  291. from plotly.graph_objects import Figure, layout
  292. from plotly.io import to_json
  293. @serializer
  294. def serialize_figure(figure: Figure) -> dict:
  295. """Serialize a plotly figure.
  296. Args:
  297. figure: The figure to serialize.
  298. Returns:
  299. The serialized figure.
  300. """
  301. return json.loads(str(to_json(figure)))
  302. @serializer
  303. def serialize_template(template: layout.Template) -> dict:
  304. """Serialize a plotly template.
  305. Args:
  306. template: The template to serialize.
  307. Returns:
  308. The serialized template.
  309. """
  310. return {
  311. "data": json.loads(str(to_json(template.data))),
  312. "layout": json.loads(str(to_json(template.layout))),
  313. }
  314. except ImportError:
  315. pass
  316. try:
  317. import base64
  318. import io
  319. from PIL.Image import MIME
  320. from PIL.Image import Image as Img
  321. @serializer
  322. def serialize_image(image: Img) -> str:
  323. """Serialize a plotly figure.
  324. Args:
  325. image: The image to serialize.
  326. Returns:
  327. The serialized image.
  328. """
  329. buff = io.BytesIO()
  330. image_format = getattr(image, "format", None) or "PNG"
  331. image.save(buff, format=image_format)
  332. image_bytes = buff.getvalue()
  333. base64_image = base64.b64encode(image_bytes).decode("utf-8")
  334. try:
  335. # Newer method to get the mime type, but does not always work.
  336. mime_type = image.get_format_mimetype() # type: ignore
  337. except AttributeError:
  338. try:
  339. # Fallback method
  340. mime_type = MIME[image_format]
  341. except KeyError:
  342. # Unknown mime_type: warn and return image/png and hope the browser can sort it out.
  343. warnings.warn( # noqa: B028
  344. f"Unknown mime type for {image} {image_format}. Defaulting to image/png"
  345. )
  346. mime_type = "image/png"
  347. return f"data:{mime_type};base64,{base64_image}"
  348. except ImportError:
  349. pass