|
9 | 9 | isasyncgenfunction,
|
10 | 10 | isasyncgen,
|
11 | 11 | )
|
12 |
| -from typing import Any |
| 12 | +from typing import Any, List |
13 | 13 |
|
14 | 14 | from ..error import INVALID
|
15 | 15 |
|
| 16 | +__all__ = ["inspect"] |
16 | 17 |
|
17 |
| -def inspect(value: Any) -> str: |
| 18 | + |
| 19 | +def inspect(value: Any, max_depth: int = 2, depth: int = 0) -> str: |
18 | 20 | """Inspect value and a return string representation for error messages.
|
19 | 21 |
|
20 | 22 | Used to print values in error messages. We do not use repr() in order to not
|
21 | 23 | leak too much of the inner Python representation of unknown objects, and we
|
22 | 24 | do not use json.dumps() because not all objects can be serialized as JSON and
|
23 | 25 | we want to output strings with single quotes like Python repr() does it.
|
| 26 | +
|
| 27 | + We also restrict the size of the representation by truncating strings and |
| 28 | + collections and allowing only a maximum recursion depth. |
24 | 29 | """
|
25 |
| - if isinstance(value, (bool, int, float, str)) or value in (None, INVALID): |
| 30 | + if value is None or value is INVALID or isinstance(value, (bool, float, complex)): |
26 | 31 | return repr(value)
|
27 |
| - # check if we have a custom inspect method |
28 |
| - try: |
29 |
| - inspect_method = value.__inspect__ |
30 |
| - if callable(inspect_method): |
31 |
| - value = inspect_method() |
32 |
| - return value if isinstance(value, str) else inspect(value) |
33 |
| - except AttributeError: |
34 |
| - pass |
35 |
| - if isinstance(value, list): |
36 |
| - return f"[{', '.join(map(inspect, value))}]" |
37 |
| - if isinstance(value, tuple): |
38 |
| - if len(value) == 1: |
39 |
| - return f"({inspect(value[0])},)" |
40 |
| - return f"({', '.join(map(inspect, value))})" |
41 |
| - if isinstance(value, dict): |
42 |
| - return ( |
43 |
| - "{" |
44 |
| - + ", ".join( |
45 |
| - map(lambda i: f"{inspect(i[0])}: {inspect(i[1])}", value.items()) |
46 |
| - ) |
47 |
| - + "}" |
48 |
| - ) |
49 |
| - if isinstance(value, set): |
50 |
| - if not len(value): |
51 |
| - return "<empty set>" |
52 |
| - return "{" + ", ".join(map(inspect, value)) + "}" |
| 32 | + if isinstance(value, (int, str, bytes, bytearray)): |
| 33 | + return trunc_str(repr(value)) |
| 34 | + if depth < max_depth: |
| 35 | + try: |
| 36 | + # check if we have a custom inspect method |
| 37 | + inspect_method = value.__inspect__ |
| 38 | + if callable(inspect_method): |
| 39 | + s = inspect_method() |
| 40 | + return ( |
| 41 | + trunc_str(s) |
| 42 | + if isinstance(s, str) |
| 43 | + else inspect(s, max_depth, depth + 1) |
| 44 | + ) |
| 45 | + except AttributeError: |
| 46 | + pass |
| 47 | + if isinstance(value, (list, tuple, dict, set, frozenset)): |
| 48 | + if not value: |
| 49 | + return repr(value) |
| 50 | + if isinstance(value, list): |
| 51 | + items = value |
| 52 | + elif isinstance(value, dict): |
| 53 | + items = list(value.items()) |
| 54 | + else: |
| 55 | + items = list(value) |
| 56 | + items = trunc_list(items) |
| 57 | + depth += 1 |
| 58 | + if isinstance(value, dict): |
| 59 | + s = ", ".join( |
| 60 | + "..." |
| 61 | + if v is ELLIPSIS |
| 62 | + else inspect(v[0], max_depth, depth) |
| 63 | + + ": " |
| 64 | + + inspect(v[1], max_depth, depth) |
| 65 | + for v in items |
| 66 | + ) |
| 67 | + else: |
| 68 | + s = ", ".join( |
| 69 | + "..." if v is ELLIPSIS else inspect(v, max_depth, depth) |
| 70 | + for v in items |
| 71 | + ) |
| 72 | + if isinstance(value, tuple): |
| 73 | + if len(items) == 1: |
| 74 | + return f"({s},)" |
| 75 | + return f"({s})" |
| 76 | + if isinstance(value, (dict, set)): |
| 77 | + return "{" + s + "}" |
| 78 | + if isinstance(value, frozenset): |
| 79 | + return f"frozenset({{{s}}})" |
| 80 | + return f"[{s}]" |
| 81 | + else: |
| 82 | + if isinstance(value, (list, tuple, dict, set, frozenset)): |
| 83 | + if not value: |
| 84 | + return repr(value) |
| 85 | + if isinstance(value, list): |
| 86 | + return "[...]" |
| 87 | + if isinstance(value, tuple): |
| 88 | + return "(...)" |
| 89 | + if isinstance(value, dict): |
| 90 | + return "{...}" |
| 91 | + if isinstance(value, set): |
| 92 | + return "set(...)" |
| 93 | + if isinstance(value, frozenset): |
| 94 | + return "frozenset(...)" |
53 | 95 | if isinstance(value, Exception):
|
54 | 96 | type_ = "exception"
|
55 | 97 | value = type(value)
|
@@ -95,3 +137,28 @@ def inspect(value: Any) -> str:
|
95 | 137 | return f"<{type_}>"
|
96 | 138 | else:
|
97 | 139 | return f"<{type_} {name}>"
|
| 140 | + |
| 141 | + |
| 142 | +def trunc_str(s: str, max_string=240) -> str: |
| 143 | + """Truncate strings to maximum length.""" |
| 144 | + if len(s) > max_string: |
| 145 | + i = max(0, (max_string - 3) // 2) |
| 146 | + j = max(0, max_string - 3 - i) |
| 147 | + s = s[:i] + "..." + s[-j:] |
| 148 | + return s |
| 149 | + |
| 150 | + |
| 151 | +def trunc_list(s: List, max_list=10) -> List: |
| 152 | + """Truncate lists to maximum length.""" |
| 153 | + if len(s) > max_list: |
| 154 | + i = max_list // 2 |
| 155 | + j = i - 1 |
| 156 | + s = s[:i] + [ELLIPSIS] + s[-j:] |
| 157 | + return s |
| 158 | + |
| 159 | + |
| 160 | +class InspectEllipsisType: |
| 161 | + """Singleton class for indicating ellipses in sequences.""" |
| 162 | + |
| 163 | + |
| 164 | +ELLIPSIS = InspectEllipsisType() |
0 commit comments