|
7 | 7 | from typing import Callable
|
8 | 8 | from typing import Sequence
|
9 | 9 | from typing import Tuple
|
| 10 | +import reprlib |
10 | 11 |
|
11 | 12 |
|
12 | 13 | _Writer = Callable[[str], object]
|
@@ -59,6 +60,96 @@ def setprocessor(self, tags: str | tuple[str, ...], processor: _Processor) -> No
|
59 | 60 | assert isinstance(tags, tuple)
|
60 | 61 | self._tags2proc[tags] = processor
|
61 | 62 |
|
| 63 | +def _try_repr_or_str(obj: object) -> str: |
| 64 | + try: |
| 65 | + return repr(obj) |
| 66 | + except (KeyboardInterrupt, SystemExit): |
| 67 | + raise |
| 68 | + except BaseException: |
| 69 | + return f'{type(obj).__name__}("{obj}")' |
| 70 | + |
| 71 | +def _format_repr_exception(exc: BaseException, obj: object) -> str: |
| 72 | + try: |
| 73 | + exc_info = _try_repr_or_str(exc) |
| 74 | + except (KeyboardInterrupt, SystemExit): |
| 75 | + raise |
| 76 | + except BaseException as exc: |
| 77 | + exc_info = f"unpresentable exception ({_try_repr_or_str(exc)})" |
| 78 | + return "<[{} raised in repr()] {} object at 0x{:x}>".format( |
| 79 | + exc_info, type(obj).__name__, id(obj) |
| 80 | + ) |
| 81 | + |
| 82 | +def _ellipsize(s: str, maxsize: int) -> str: |
| 83 | + if len(s) > maxsize: |
| 84 | + i = max(0, (maxsize - 3) // 2) |
| 85 | + j = max(0, maxsize - 3 - i) |
| 86 | + return s[:i] + "..." + s[len(s) - j :] |
| 87 | + return s |
| 88 | + |
| 89 | +class SafeRepr(reprlib.Repr): |
| 90 | + """ |
| 91 | + repr.Repr that limits the resulting size of repr() and includes |
| 92 | + information on exceptions raised during the call. |
| 93 | + """ |
| 94 | + |
| 95 | + def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None: |
| 96 | + """ |
| 97 | + :param maxsize: |
| 98 | + If not None, will truncate the resulting repr to that specific size, using ellipsis |
| 99 | + somewhere in the middle to hide the extra text. |
| 100 | + If None, will not impose any size limits on the returning repr. |
| 101 | + """ |
| 102 | + super().__init__() |
| 103 | + # ``maxstring`` is used by the superclass, and needs to be an int; using a |
| 104 | + # very large number in case maxsize is None, meaning we want to disable |
| 105 | + # truncation. |
| 106 | + self.maxstring = maxsize if maxsize is not None else 1_000_000_000 |
| 107 | + self.maxsize = maxsize |
| 108 | + self.use_ascii = use_ascii |
| 109 | + |
| 110 | + def repr(self, x: object) -> str: |
| 111 | + try: |
| 112 | + if self.use_ascii: |
| 113 | + s = ascii(x) |
| 114 | + else: |
| 115 | + s = super().repr(x) |
| 116 | + |
| 117 | + except (KeyboardInterrupt, SystemExit): |
| 118 | + raise |
| 119 | + except BaseException as exc: |
| 120 | + s = _format_repr_exception(exc, x) |
| 121 | + if self.maxsize is not None: |
| 122 | + s = _ellipsize(s, self.maxsize) |
| 123 | + return s |
| 124 | + |
| 125 | + def repr_instance(self, x: object, level: int) -> str: |
| 126 | + try: |
| 127 | + s = repr(x) |
| 128 | + except (KeyboardInterrupt, SystemExit): |
| 129 | + raise |
| 130 | + except BaseException as exc: |
| 131 | + s = _format_repr_exception(exc, x) |
| 132 | + if self.maxsize is not None: |
| 133 | + s = _ellipsize(s, self.maxsize) |
| 134 | + return s |
| 135 | + |
| 136 | + |
| 137 | +# Maximum size of overall repr of objects to display during assertion errors. |
| 138 | +DEFAULT_REPR_MAX_SIZE = 240 |
| 139 | +def saferepr( |
| 140 | + obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False |
| 141 | +) -> str: |
| 142 | + """Return a size-limited safe repr-string for the given object. |
| 143 | +
|
| 144 | + Failing __repr__ functions of user instances will be represented |
| 145 | + with a short exception info and 'saferepr' generally takes |
| 146 | + care to never raise exceptions itself. |
| 147 | +
|
| 148 | + This function is a wrapper around the Repr/reprlib functionality of the |
| 149 | + stdlib. |
| 150 | + """ |
| 151 | + |
| 152 | + return SafeRepr(maxsize, use_ascii).repr(obj) |
62 | 153 |
|
63 | 154 | class TagTracerSub:
|
64 | 155 | def __init__(self, root: TagTracer, tags: tuple[str, ...]) -> None:
|
|
0 commit comments