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

8

9 def _try_repr_or_str(obj: object) -> str:

10 try:

11 return repr(obj)

12 except (KeyboardInterrupt, SystemExit):

13 raise

14 except BaseException:

15 return f'{type(obj).__name__}("{obj}")'

16

17

18 def _format_repr_exception(exc: BaseException, obj: object) -> str:

19 try:

20 exc_info = _try_repr_or_str(exc)

21 except (KeyboardInterrupt, SystemExit):

22 raise

23 except BaseException as exc:

24 exc_info = f"unpresentable exception ({_try_repr_or_str(exc)})"

25 return "<[{} raised in repr()] {} object at 0x{:x}>".format(

26 exc_info, type(obj).__name__, id(obj)

27 )

28

29

30 def _ellipsize(s: str, maxsize: int) -> str:

31 if len(s) > maxsize:

32 i = max(0, (maxsize - 3) // 2)

33 j = max(0, maxsize - 3 - i)

34 return s[:i] + "..." + s[len(s) - j :]

35 return s

36

37

38 class SafeRepr(reprlib.Repr):

39 """

40 repr.Repr that limits the resulting size of repr() and includes

41 information on exceptions raised during the call.

42 """

43

44 def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None:

45 """

46 :param maxsize:

47 If not None, will truncate the resulting repr to that specific size, using ellipsis

48 somewhere in the middle to hide the extra text.

49 If None, will not impose any size limits on the returning repr.

50 """

51 super().__init__()

52 # ``maxstring`` is used by the superclass, and needs to be an int; using a

53 # very large number in case maxsize is None, meaning we want to disable

54 # truncation.

55 self.maxstring = maxsize if maxsize is not None else 1_000_000_000

56 self.maxsize = maxsize

57 self.use_ascii = use_ascii

58

59 def repr(self, x: object) -> str:

60 try:

61 if self.use_ascii:

62 s = ascii(x)

63 else:

64 s = super().repr(x)

65

66 except (KeyboardInterrupt, SystemExit):

67 raise

68 except BaseException as exc:

69 s = _format_repr_exception(exc, x)

70 if self.maxsize is not None:

71 s = _ellipsize(s, self.maxsize)

72 return s

73

74 def repr_instance(self, x: object, level: int) -> str:

75 try:

76 s = repr(x)

77 except (KeyboardInterrupt, SystemExit):

78 raise

79 except BaseException as exc:

80 s = _format_repr_exception(exc, x)

81 if self.maxsize is not None:

82 s = _ellipsize(s, self.maxsize)

83 return s

84

85

86 def safeformat(obj: object) -> str:

87 """Return a pretty printed string for the given object.

88

89 Failing __repr__ functions of user instances will be represented

90 with a short exception info.

91 """

92 try:

93 return pprint.pformat(obj)

94 except Exception as exc:

95 return _format_repr_exception(exc, obj)

96

97

98# Maximum size of overall repr of objects to display during assertion errors.

99 DEFAULT_REPR_MAX_SIZE = 240

100

101

102 def saferepr(

103 obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False

104) -> str:

105 """Return a size-limited safe repr-string for the given object.

106

107 Failing __repr__ functions of user instances will be represented

108 with a short exception info and 'saferepr' generally takes

109 care to never raise exceptions itself.

110

111 This function is a wrapper around the Repr/reprlib functionality of the

112 stdlib.

113 """

114

115 return SafeRepr(maxsize, use_ascii).repr(obj)

116

117

118 def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str:

119 """Return an unlimited-size safe repr-string for the given object.

120

121 As with saferepr, failing __repr__ functions of user instances

122 will be represented with a short exception info.

123

124 This function is a wrapper around simple repr.

125

126 Note: a cleaner solution would be to alter ``saferepr``this way

127 when maxsize=None, but that might affect some other code.

128 """

129 try:

130 if use_ascii:

131 return ascii(obj)

132 return repr(obj)

133 except Exception as exc:

134 return _format_repr_exception(exc, obj)

135

136

137 class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter):

138 """PrettyPrinter that always dispatches (regardless of width)."""

139

140 def _format(

141 self,

142 object: object,

143 stream: IO[str],

144 indent: int,

145 allowance: int,

146 context: Dict[int, Any],

147 level: int,

148 ) -> None:

149 # Type ignored because _dispatch is private.

150 p = self._dispatch.get(type(object).__repr__, None) # type: ignore[attr-defined]

151

152 objid = id(object)

153 if objid in context or p is None:

154 # Type ignored because _format is private.

155 super()._format( # type: ignore[misc]

156 object,

157 stream,

158 indent,

159 allowance,

160 context,

161 level,

162 )

163 return

164

165 context[objid] = 1

166 p(self, object, stream, indent, allowance, context, level + 1)

167 del context[objid]

168

169

170 def _pformat_dispatch(

171 object: object,

172 indent: int = 1,

173 width: int = 80,

174 depth: Optional[int] = None,

175 *,

176 compact: bool = False,

177) -> str:

178 return AlwaysDispatchingPrettyPrinter(

179 indent=indent, width=width, depth=depth, compact=compact

180 ).pformat(object)