defaults.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. """Collection of functions for building custom `json_default` functions.
  2. In general functions come in pairs of `use_x_default` and `x_default`, where the former is used
  3. to determine if you should call the latter.
  4. Most `use_x_default` functions also act as a [`TypeGuard`](https://mypy.readthedocs.io/en/stable/type_narrowing.html#user-defined-type-guards).
  5. """
  6. ### IMPORTS
  7. ### ============================================================================
  8. ## Future
  9. from __future__ import annotations
  10. ## Standard Library
  11. import base64
  12. import dataclasses
  13. import datetime
  14. import enum
  15. from types import TracebackType
  16. from typing import Any, TypeGuard
  17. import traceback
  18. import uuid
  19. ## Installed
  20. ## Application
  21. ### FUNCTIONS
  22. ### ============================================================================
  23. def unknown_default(obj: Any) -> str:
  24. """Backup default function for any object type.
  25. Will attempt to use `str` or `repr`. If both functions error will return
  26. the string `"__could_not_encode__"`.
  27. Args:
  28. obj: object to handle
  29. """
  30. try:
  31. return str(obj)
  32. except Exception: # pylint: disable=broad-exception-caught
  33. pass
  34. try:
  35. return repr(obj)
  36. except Exception: # pylint: disable=broad-exception-caught
  37. pass
  38. return "__could_not_encode__"
  39. ## Types
  40. ## -----------------------------------------------------------------------------
  41. def use_type_default(obj: Any) -> TypeGuard[type]:
  42. """Default check function for `type` objects (aka classes)."""
  43. return isinstance(obj, type)
  44. def type_default(obj: type) -> str:
  45. """Default function for `type` objects.
  46. Args:
  47. obj: object to handle
  48. """
  49. return obj.__name__
  50. ## Dataclasses
  51. ## -----------------------------------------------------------------------------
  52. def use_dataclass_default(obj: Any) -> bool:
  53. """Default check function for dataclass instances"""
  54. return dataclasses.is_dataclass(obj) and not isinstance(obj, type)
  55. def dataclass_default(obj) -> dict[str, Any]:
  56. """Default function for dataclass instances
  57. Args:
  58. obj: object to handle
  59. """
  60. return dataclasses.asdict(obj)
  61. ## Dates and Times
  62. ## -----------------------------------------------------------------------------
  63. def use_time_default(obj: Any) -> TypeGuard[datetime.time]:
  64. """Default check function for `datetime.time` instances"""
  65. return isinstance(obj, datetime.time)
  66. def time_default(obj: datetime.time) -> str:
  67. """Default function for `datetime.time` instances
  68. Args:
  69. obj: object to handle
  70. """
  71. return obj.isoformat()
  72. def use_date_default(obj: Any) -> TypeGuard[datetime.date]:
  73. """Default check function for `datetime.date` instances"""
  74. return isinstance(obj, datetime.date)
  75. def date_default(obj: datetime.date) -> str:
  76. """Default function for `datetime.date` instances
  77. Args:
  78. obj: object to handle
  79. """
  80. return obj.isoformat()
  81. def use_datetime_default(obj: Any) -> TypeGuard[datetime.datetime]:
  82. """Default check function for `datetime.datetime` instances"""
  83. return isinstance(obj, datetime.datetime)
  84. def datetime_default(obj: datetime.datetime) -> str:
  85. """Default function for `datetime.datetime` instances
  86. Args:
  87. obj: object to handle
  88. """
  89. return obj.isoformat()
  90. def use_datetime_any(obj: Any) -> TypeGuard[datetime.time | datetime.date | datetime.datetime]:
  91. """Default check function for `datetime` related instances"""
  92. return isinstance(obj, (datetime.time, datetime.date, datetime.datetime))
  93. def datetime_any(obj: datetime.time | datetime.date | datetime.date) -> str:
  94. """Default function for `datetime` related instances
  95. Args:
  96. obj: object to handle
  97. """
  98. return obj.isoformat()
  99. ## Exception and Tracebacks
  100. ## -----------------------------------------------------------------------------
  101. def use_exception_default(obj: Any) -> TypeGuard[BaseException]:
  102. """Default check function for exception instances.
  103. Exception classes are not treated specially and should be handled by the
  104. `[use_]type_default` functions.
  105. """
  106. return isinstance(obj, BaseException)
  107. def exception_default(obj: BaseException) -> str:
  108. """Default function for exception instances
  109. Args:
  110. obj: object to handle
  111. """
  112. return f"{obj.__class__.__name__}: {obj}"
  113. def use_traceback_default(obj: Any) -> TypeGuard[TracebackType]:
  114. """Default check function for tracebacks"""
  115. return isinstance(obj, TracebackType)
  116. def traceback_default(obj: TracebackType) -> str:
  117. """Default function for tracebacks
  118. Args:
  119. obj: object to handle
  120. """
  121. return "".join(traceback.format_tb(obj)).strip()
  122. ## Enums
  123. ## -----------------------------------------------------------------------------
  124. def use_enum_default(obj: Any) -> TypeGuard[enum.Enum | enum.EnumMeta]:
  125. """Default check function for enums.
  126. Supports both enum classes and enum values.
  127. """
  128. return isinstance(obj, (enum.Enum, enum.EnumMeta))
  129. def enum_default(obj: enum.Enum | enum.EnumMeta) -> Any | list[Any]:
  130. """Default function for enums.
  131. Supports both enum classes and enum values.
  132. Args:
  133. obj: object to handle
  134. """
  135. if isinstance(obj, enum.Enum):
  136. return obj.value
  137. return [e.value for e in obj] # type: ignore[var-annotated]
  138. ## UUIDs
  139. ## -----------------------------------------------------------------------------
  140. def use_uuid_default(obj: Any) -> TypeGuard[uuid.UUID]:
  141. """Default check function for `uuid.UUID` instances"""
  142. return isinstance(obj, uuid.UUID)
  143. def uuid_default(obj: uuid.UUID) -> str:
  144. """Default function for `uuid.UUID` instances
  145. Formats the UUID using "hyphen" format.
  146. Args:
  147. obj: object to handle
  148. """
  149. return str(obj)
  150. ## Bytes
  151. ## -----------------------------------------------------------------------------
  152. def use_bytes_default(obj: Any) -> TypeGuard[bytes | bytearray]:
  153. """Default check function for bytes"""
  154. return isinstance(obj, (bytes, bytearray))
  155. def bytes_default(obj: bytes | bytearray, url_safe: bool = True) -> str:
  156. """Default function for bytes
  157. Args:
  158. obj: object to handle
  159. url_safe: use URL safe base 64 character set.
  160. Returns:
  161. The byte data as a base 64 string.
  162. """
  163. if url_safe:
  164. return base64.urlsafe_b64encode(obj).decode("utf8")
  165. return base64.b64encode(obj).decode("utf8")