-
-
Notifications
You must be signed in to change notification settings - Fork 145
Expand file tree
/
Copy pathinspect.py
More file actions
182 lines (162 loc) · 5.65 KB
/
inspect.py
File metadata and controls
182 lines (162 loc) · 5.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
"""Value inspection for error messages"""
from __future__ import annotations
from inspect import (
isasyncgen,
isasyncgenfunction,
isclass,
iscoroutine,
iscoroutinefunction,
isfunction,
isgenerator,
isgeneratorfunction,
ismethod,
)
from typing import Any
from .undefined import Undefined
__all__ = ["inspect"]
max_recursive_depth = 2
max_str_size = 240
max_list_size = 10
def inspect(value: Any) -> str:
"""Inspect value and a return string representation for error messages.
Used to print values in error messages. We do not use repr() in order to not
leak too much of the inner Python representation of unknown objects, and we
do not use json.dumps() because not all objects can be serialized as JSON and
we want to output strings with single quotes like Python repr() does it.
We also restrict the size of the representation by truncating strings and
collections and allowing only a maximum recursion depth.
"""
return inspect_recursive(value, [])
def inspect_recursive(value: Any, seen_values: list) -> str:
if value is None or value is Undefined or isinstance(value, (bool, float, complex)):
return repr(value)
if isinstance(value, (int, str, bytes, bytearray)):
return trunc_str(repr(value))
if len(seen_values) < max_recursive_depth and value not in seen_values:
# check if we have a custom inspect method
inspect_method = getattr(value, "__inspect__", None)
if inspect_method is not None and callable(inspect_method):
s = inspect_method()
if isinstance(s, str):
return trunc_str(s)
seen_values = [*seen_values, value]
return inspect_recursive(s, seen_values)
# recursively inspect collections
if isinstance(value, (list, tuple, dict, set, frozenset)):
if not value:
return repr(value)
seen_values = [*seen_values, value]
if isinstance(value, list):
items = value
elif isinstance(value, dict):
items = list(value.items())
else:
items = list(value)
items = trunc_list(items)
if isinstance(value, dict):
s = ", ".join(
"..."
if v is ELLIPSIS
else inspect_recursive(v[0], seen_values)
+ ": "
+ inspect_recursive(v[1], seen_values)
for v in items
)
else:
s = ", ".join(
"..." if v is ELLIPSIS else inspect_recursive(v, seen_values)
for v in items
)
if isinstance(value, tuple):
if len(items) == 1:
return f"({s},)"
return f"({s})"
if isinstance(value, (dict, set)):
return "{" + s + "}"
if isinstance(value, frozenset):
return f"frozenset({{{s}}})"
return f"[{s}]"
elif isinstance(value, (list, tuple, dict, set, frozenset)):
if not value:
return repr(value)
if isinstance(value, list):
return "[...]"
if isinstance(value, tuple):
return "(...)"
if isinstance(value, dict):
return "{...}"
if isinstance(value, set):
return "set(...)"
return "frozenset(...)"
if isinstance(value, Exception):
type_ = "exception"
value = type(value)
elif isclass(value):
type_ = "exception class" if issubclass(value, Exception) else "class"
elif ismethod(value):
type_ = "method"
elif iscoroutinefunction(value):
type_ = "coroutine function"
elif isasyncgenfunction(value):
type_ = "async generator function"
elif isgeneratorfunction(value):
type_ = "generator function"
elif isfunction(value):
type_ = "function"
elif iscoroutine(value):
type_ = "coroutine"
elif isasyncgen(value):
type_ = "async generator"
elif isgenerator(value):
type_ = "generator"
else:
# stringify (only) the well-known GraphQL types
from ..type import (
GraphQLDirective,
GraphQLNamedType,
GraphQLScalarType,
GraphQLWrappingType,
)
if isinstance(
value,
(
GraphQLDirective,
GraphQLNamedType,
GraphQLScalarType,
GraphQLWrappingType,
),
):
return str(value)
try:
name = type(value).__name__
if not name or "<" in name or ">" in name:
raise AttributeError # noqa: TRY301
except AttributeError:
return "<object>"
else:
return f"<{name} instance>"
try:
name = value.__name__
if not name or "<" in name or ">" in name:
raise AttributeError # noqa: TRY301
except AttributeError:
return f"<{type_}>"
else:
return f"<{type_} {name}>"
def trunc_str(s: str) -> str:
"""Truncate strings to maximum length."""
if len(s) > max_str_size:
i = max(0, (max_str_size - 3) // 2)
j = max(0, max_str_size - 3 - i)
s = s[:i] + "..." + s[-j:]
return s
def trunc_list(s: list) -> list:
"""Truncate lists to maximum length."""
if len(s) > max_list_size:
i = max_list_size // 2
j = i - 1
s = [*s[:i], ELLIPSIS, *s[-j:]]
return s
class InspectEllipsisType:
"""Singleton class for indicating ellipses in iterables."""
ELLIPSIS = InspectEllipsisType()