saferepr.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import pprint
  2. import reprlib
  3. from typing import Any
  4. from typing import Dict
  5. from typing import IO
  6. from typing import Optional
  7. def _try_repr_or_str(obj: object) -> str:
  8. try:
  9. return repr(obj)
  10. except (KeyboardInterrupt, SystemExit):
  11. raise
  12. except BaseException:
  13. return f'{type(obj).__name__}("{obj}")'
  14. def _format_repr_exception(exc: BaseException, obj: object) -> str:
  15. try:
  16. exc_info = _try_repr_or_str(exc)
  17. except (KeyboardInterrupt, SystemExit):
  18. raise
  19. except BaseException as exc:
  20. exc_info = f"unpresentable exception ({_try_repr_or_str(exc)})"
  21. return "<[{} raised in repr()] {} object at 0x{:x}>".format(
  22. exc_info, type(obj).__name__, id(obj)
  23. )
  24. def _ellipsize(s: str, maxsize: int) -> str:
  25. if len(s) > maxsize:
  26. i = max(0, (maxsize - 3) // 2)
  27. j = max(0, maxsize - 3 - i)
  28. return s[:i] + "..." + s[len(s) - j :]
  29. return s
  30. class SafeRepr(reprlib.Repr):
  31. """
  32. repr.Repr that limits the resulting size of repr() and includes
  33. information on exceptions raised during the call.
  34. """
  35. def __init__(self, maxsize: Optional[int]) -> None:
  36. """
  37. :param maxsize:
  38. If not None, will truncate the resulting repr to that specific size, using ellipsis
  39. somewhere in the middle to hide the extra text.
  40. If None, will not impose any size limits on the returning repr.
  41. """
  42. super().__init__()
  43. # ``maxstring`` is used by the superclass, and needs to be an int; using a
  44. # very large number in case maxsize is None, meaning we want to disable
  45. # truncation.
  46. self.maxstring = maxsize if maxsize is not None else 1_000_000_000
  47. self.maxsize = maxsize
  48. def repr(self, x: object) -> str:
  49. try:
  50. s = super().repr(x)
  51. except (KeyboardInterrupt, SystemExit):
  52. raise
  53. except BaseException as exc:
  54. s = _format_repr_exception(exc, x)
  55. if self.maxsize is not None:
  56. s = _ellipsize(s, self.maxsize)
  57. return s
  58. def repr_instance(self, x: object, level: int) -> str:
  59. try:
  60. s = repr(x)
  61. except (KeyboardInterrupt, SystemExit):
  62. raise
  63. except BaseException as exc:
  64. s = _format_repr_exception(exc, x)
  65. if self.maxsize is not None:
  66. s = _ellipsize(s, self.maxsize)
  67. return s
  68. def safeformat(obj: object) -> str:
  69. """Return a pretty printed string for the given object.
  70. Failing __repr__ functions of user instances will be represented
  71. with a short exception info.
  72. """
  73. try:
  74. return pprint.pformat(obj)
  75. except Exception as exc:
  76. return _format_repr_exception(exc, obj)
  77. # Maximum size of overall repr of objects to display during assertion errors.
  78. DEFAULT_REPR_MAX_SIZE = 240
  79. def saferepr(obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE) -> str:
  80. """Return a size-limited safe repr-string for the given object.
  81. Failing __repr__ functions of user instances will be represented
  82. with a short exception info and 'saferepr' generally takes
  83. care to never raise exceptions itself.
  84. This function is a wrapper around the Repr/reprlib functionality of the
  85. stdlib.
  86. """
  87. return SafeRepr(maxsize).repr(obj)
  88. class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter):
  89. """PrettyPrinter that always dispatches (regardless of width)."""
  90. def _format(
  91. self,
  92. object: object,
  93. stream: IO[str],
  94. indent: int,
  95. allowance: int,
  96. context: Dict[int, Any],
  97. level: int,
  98. ) -> None:
  99. # Type ignored because _dispatch is private.
  100. p = self._dispatch.get(type(object).__repr__, None) # type: ignore[attr-defined]
  101. objid = id(object)
  102. if objid in context or p is None:
  103. # Type ignored because _format is private.
  104. super()._format( # type: ignore[misc]
  105. object,
  106. stream,
  107. indent,
  108. allowance,
  109. context,
  110. level,
  111. )
  112. return
  113. context[objid] = 1
  114. p(self, object, stream, indent, allowance, context, level + 1)
  115. del context[objid]
  116. def _pformat_dispatch(
  117. object: object,
  118. indent: int = 1,
  119. width: int = 80,
  120. depth: Optional[int] = None,
  121. *,
  122. compact: bool = False,
  123. ) -> str:
  124. return AlwaysDispatchingPrettyPrinter(
  125. indent=indent, width=width, depth=depth, compact=compact
  126. ).pformat(object)