serializers.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  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. ...
  77. @overload
  78. def serialize(value: Any, get_type: Literal[False]) -> Optional[SerializedType]:
  79. ...
  80. @overload
  81. def serialize(value: Any) -> Optional[SerializedType]:
  82. ...
  83. def serialize(
  84. value: Any, get_type: bool = False
  85. ) -> Union[
  86. Optional[SerializedType],
  87. Tuple[Optional[SerializedType], Optional[types.GenericType]],
  88. ]:
  89. """Serialize the value to a JSON string.
  90. Args:
  91. value: The value to serialize.
  92. get_type: Whether to return the type of the serialized value.
  93. Returns:
  94. The serialized value, or None if a serializer is not found.
  95. """
  96. # Get the serializer for the type.
  97. serializer = get_serializer(type(value))
  98. # If there is no serializer, return None.
  99. if serializer is None:
  100. if get_type:
  101. return None, None
  102. return None
  103. # Serialize the value.
  104. serialized = serializer(value)
  105. # Return the serialized value and the type.
  106. if get_type:
  107. return serialized, get_serializer_type(type(value))
  108. else:
  109. return serialized
  110. @functools.lru_cache
  111. def get_serializer(type_: Type) -> Optional[Serializer]:
  112. """Get the serializer for the type.
  113. Args:
  114. type_: The type to get the serializer for.
  115. Returns:
  116. The serializer for the type, or None if there is no serializer.
  117. """
  118. # First, check if the type is registered.
  119. serializer = SERIALIZERS.get(type_)
  120. if serializer is not None:
  121. return serializer
  122. # If the type is not registered, check if it is a subclass of a registered type.
  123. for registered_type, serializer in reversed(SERIALIZERS.items()):
  124. if types._issubclass(type_, registered_type):
  125. return serializer
  126. # If there is no serializer, return None.
  127. return None
  128. @functools.lru_cache
  129. def get_serializer_type(type_: Type) -> Optional[Type]:
  130. """Get the converted type for the type after serializing.
  131. Args:
  132. type_: The type to get the serializer type for.
  133. Returns:
  134. The serialized type for the type, or None if there is no type conversion registered.
  135. """
  136. # First, check if the type is registered.
  137. serializer = SERIALIZER_TYPES.get(type_)
  138. if serializer is not None:
  139. return serializer
  140. # If the type is not registered, check if it is a subclass of a registered type.
  141. for registered_type, serializer in reversed(SERIALIZER_TYPES.items()):
  142. if types._issubclass(type_, registered_type):
  143. return serializer
  144. # If there is no serializer, return None.
  145. return None
  146. def has_serializer(type_: Type) -> bool:
  147. """Check if there is a serializer for the type.
  148. Args:
  149. type_: The type to check.
  150. Returns:
  151. Whether there is a serializer for the type.
  152. """
  153. return get_serializer(type_) is not None
  154. @serializer(to=str)
  155. def serialize_type(value: type) -> str:
  156. """Serialize a python type.
  157. Args:
  158. value: the type to serialize.
  159. Returns:
  160. The serialized type.
  161. """
  162. return value.__name__
  163. @serializer
  164. def serialize_str(value: str) -> str:
  165. """Serialize a string.
  166. Args:
  167. value: The string to serialize.
  168. Returns:
  169. The serialized string.
  170. """
  171. return value
  172. @serializer
  173. def serialize_primitive(value: Union[bool, int, float, None]) -> str:
  174. """Serialize a primitive type.
  175. Args:
  176. value: The number/bool/None to serialize.
  177. Returns:
  178. The serialized number/bool/None.
  179. """
  180. from reflex.utils import format
  181. return format.json_dumps(value)
  182. @serializer
  183. def serialize_base(value: Base) -> str:
  184. """Serialize a Base instance.
  185. Args:
  186. value : The Base to serialize.
  187. Returns:
  188. The serialized Base.
  189. """
  190. return value.json()
  191. @serializer
  192. def serialize_list(value: Union[List, Tuple, Set]) -> str:
  193. """Serialize a list to a JSON string.
  194. Args:
  195. value: The list to serialize.
  196. Returns:
  197. The serialized list.
  198. """
  199. from reflex.utils import format
  200. # Dump the list to a string.
  201. fprop = format.json_dumps(list(value))
  202. # Unwrap var values.
  203. return format.unwrap_vars(fprop)
  204. @serializer
  205. def serialize_dict(prop: Dict[str, Any]) -> str:
  206. """Serialize a dictionary to a JSON string.
  207. Args:
  208. prop: The dictionary to serialize.
  209. Returns:
  210. The serialized dictionary.
  211. Raises:
  212. InvalidStylePropError: If the style prop is invalid.
  213. """
  214. # Import here to avoid circular imports.
  215. from reflex.event import EventHandler
  216. from reflex.utils import format
  217. prop_dict = {}
  218. for key, value in prop.items():
  219. if types._issubclass(type(value), Callable):
  220. raise exceptions.InvalidStylePropError(
  221. f"The style prop `{format.to_snake_case(key)}` cannot have " # type: ignore
  222. f"`{value.fn.__qualname__ if isinstance(value, EventHandler) else value.__qualname__ if isinstance(value, builtin_types.FunctionType) else value}`, "
  223. f"an event handler or callable as its value"
  224. )
  225. prop_dict[key] = value
  226. # Dump the dict to a string.
  227. fprop = format.json_dumps(prop_dict)
  228. # Unwrap var values.
  229. return format.unwrap_vars(fprop)
  230. @serializer(to=str)
  231. def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
  232. """Serialize a datetime to a JSON string.
  233. Args:
  234. dt: The datetime to serialize.
  235. Returns:
  236. The serialized datetime.
  237. """
  238. return str(dt)
  239. @serializer(to=str)
  240. def serialize_path(path: Path) -> str:
  241. """Serialize a pathlib.Path to a JSON string.
  242. Args:
  243. path: The path to serialize.
  244. Returns:
  245. The serialized path.
  246. """
  247. return str(path.as_posix())
  248. @serializer
  249. def serialize_enum(en: Enum) -> str:
  250. """Serialize a enum to a JSON string.
  251. Args:
  252. en: The enum to serialize.
  253. Returns:
  254. The serialized enum.
  255. """
  256. return en.value
  257. @serializer(to=str)
  258. def serialize_color(color: Color) -> str:
  259. """Serialize a color.
  260. Args:
  261. color: The color to serialize.
  262. Returns:
  263. The serialized color.
  264. """
  265. return format_color(color.color, color.shade, color.alpha)
  266. try:
  267. from pandas import DataFrame
  268. def format_dataframe_values(df: DataFrame) -> List[List[Any]]:
  269. """Format dataframe values to a list of lists.
  270. Args:
  271. df: The dataframe to format.
  272. Returns:
  273. The dataframe as a list of lists.
  274. """
  275. return [
  276. [str(d) if isinstance(d, (list, tuple)) else d for d in data]
  277. for data in list(df.values.tolist())
  278. ]
  279. @serializer
  280. def serialize_dataframe(df: DataFrame) -> dict:
  281. """Serialize a pandas dataframe.
  282. Args:
  283. df: The dataframe to serialize.
  284. Returns:
  285. The serialized dataframe.
  286. """
  287. return {
  288. "columns": df.columns.tolist(),
  289. "data": format_dataframe_values(df),
  290. }
  291. except ImportError:
  292. pass
  293. try:
  294. from plotly.graph_objects import Figure, layout
  295. from plotly.io import to_json
  296. @serializer
  297. def serialize_figure(figure: Figure) -> dict:
  298. """Serialize a plotly figure.
  299. Args:
  300. figure: The figure to serialize.
  301. Returns:
  302. The serialized figure.
  303. """
  304. return json.loads(str(to_json(figure)))
  305. @serializer
  306. def serialize_template(template: layout.Template) -> dict:
  307. """Serialize a plotly template.
  308. Args:
  309. template: The template to serialize.
  310. Returns:
  311. The serialized template.
  312. """
  313. return {
  314. "data": json.loads(str(to_json(template.data))),
  315. "layout": json.loads(str(to_json(template.layout))),
  316. }
  317. except ImportError:
  318. pass
  319. try:
  320. import base64
  321. import io
  322. from PIL.Image import MIME
  323. from PIL.Image import Image as Img
  324. @serializer
  325. def serialize_image(image: Img) -> str:
  326. """Serialize a plotly figure.
  327. Args:
  328. image: The image to serialize.
  329. Returns:
  330. The serialized image.
  331. """
  332. buff = io.BytesIO()
  333. image_format = getattr(image, "format", None) or "PNG"
  334. image.save(buff, format=image_format)
  335. image_bytes = buff.getvalue()
  336. base64_image = base64.b64encode(image_bytes).decode("utf-8")
  337. try:
  338. # Newer method to get the mime type, but does not always work.
  339. mime_type = image.get_format_mimetype() # type: ignore
  340. except AttributeError:
  341. try:
  342. # Fallback method
  343. mime_type = MIME[image_format]
  344. except KeyError:
  345. # Unknown mime_type: warn and return image/png and hope the browser can sort it out.
  346. warnings.warn( # noqa: B028
  347. f"Unknown mime type for {image} {image_format}. Defaulting to image/png"
  348. )
  349. mime_type = "image/png"
  350. return f"data:{mime_type};base64,{base64_image}"
  351. except ImportError:
  352. pass