1 """Object representations for debugging purposes. Unlike the default
2 repr, these expose more information and produce HTML instead of ASCII.
4 Together with the CSS and JavaScript of the debugger this gives a
5 colorful and more compact output.
7 from __future__
import annotations
13 from collections
import deque
14 from traceback
import format_exception_only
16 from markupsafe
import escape
19 _paragraph_re
= re
.compile(r
"(?:\r\n|\r|\n){2,}")
20 RegexType
= type(_paragraph_re
)
25 <pre class=help>%(text)s</pre>
28 OBJECT_DUMP_HTML
= """\
32 <table>%(items)s</table>
37 def debug_repr(obj
: object) -> str:
38 """Creates a debug repr of an object as HTML string."""
39 return DebugReprGenerator().repr(obj
)
42 def dump(obj
: object = missing
) -> None:
43 """Print the object details to stdout._write (for the interactive
44 console of the web debugger.
46 gen
= DebugReprGenerator()
48 rv
= gen
.dump_locals(sys
._getframe
(1).f_locals
)
50 rv
= gen
.dump_object(obj
)
51 sys
.stdout
._write
(rv
) # type: ignore
55 """Displays an HTML version of the normal help, for the interactive
56 debugger only because it requires a patched sys.stdout.
59 def __repr__(self
) -> str:
60 return "Type help(object) for help about object."
62 def __call__(self
, topic
: t
.Any |
None = None) -> None:
64 sys
.stdout
._write
(f
"<span class=help>{self!r}</span>") # type: ignore
69 rv
= sys
.stdout
.reset() # type: ignore
70 paragraphs
= _paragraph_re
.split(rv
)
71 if len(paragraphs
) > 1:
73 text
= "\n\n".join(paragraphs
[1:])
77 sys
.stdout
._write
(HELP_HTML
% {"title": title, "text": text}
) # type: ignore
83 def _add_subclass_info(
84 inner
: str, obj
: object, base
: t
.Type |
tuple[t
.Type
, ...]
86 if isinstance(base
, tuple):
90 elif type(obj
) is base
:
93 if obj
.__class
__.__module
__ not in ("__builtin__", "exceptions"):
94 module
= f
'<span class="module">{obj.__class__.__module__}.</span>'
95 return f
"{module}{type(obj).__name__}({inner})"
98 def _sequence_repr_maker(
99 left
: str, right
: str, base
: t
.Type
, limit
: int = 8
100 ) -> t
.Callable
[[DebugReprGenerator
, t
.Iterable
, bool], str]:
101 def proxy(self
: DebugReprGenerator
, obj
: t
.Iterable
, recursive
: bool) -> str:
103 return _add_subclass_info(f
"{left}...{right}", obj
, base
)
105 have_extended_section
= False
106 for idx
, item
in enumerate(obj
):
110 buf
.append('<span class="extended">')
111 have_extended_section
= True
112 buf
.append(self
.repr(item
))
113 if have_extended_section
:
114 buf
.append("</span>")
116 return _add_subclass_info("".join(buf
), obj
, base
)
121 class DebugReprGenerator
:
122 def __init__(self
) -> None:
123 self
._stack
: list[t
.Any
] = []
125 list_repr
= _sequence_repr_maker("[", "]", list)
126 tuple_repr
= _sequence_repr_maker("(", ")", tuple)
127 set_repr
= _sequence_repr_maker("set([", "])", set)
128 frozenset_repr
= _sequence_repr_maker("frozenset([", "])", frozenset)
129 deque_repr
= _sequence_repr_maker(
130 '<span class="module">collections.</span>deque([', "])", deque
133 def regex_repr(self
, obj
: t
.Pattern
) -> str:
134 pattern
= repr(obj
.pattern
)
135 pattern
= codecs
.decode(pattern
, "unicode-escape", "ignore")
136 pattern
= f
"r{pattern}"
137 return f
're.compile(<span class="string regex">{pattern}</span>)'
139 def string_repr(self
, obj
: str |
bytes, limit
: int = 70) -> str:
140 buf
= ['<span class="string">']
143 # shorten the repr when the hidden part would be at least 3 chars
144 if len(r
) - limit
> 2:
148 '<span class="extended">',
154 buf
.append(escape(r
))
156 buf
.append("</span>")
159 # if the repr looks like a standard string, add subclass info if needed
160 if r
[0] in "'\"" or (r
[0] == "b" and r
[1] in "'\""):
161 return _add_subclass_info(out
, obj
, (bytes, str))
163 # otherwise, assume the repr distinguishes the subclass already
168 d
: dict[int, None] |
dict[str, int] |
dict[str |
int, int],
173 return _add_subclass_info("{...}", d
, dict)
175 have_extended_section
= False
176 for idx
, (key
, value
) in enumerate(d
.items()):
180 buf
.append('<span class="extended">')
181 have_extended_section
= True
183 f
'<span class="pair"><span class="key">{self.repr(key)}</span>:'
184 f
' <span class="value">{self.repr(value)}</span></span>'
186 if have_extended_section
:
187 buf
.append("</span>")
189 return _add_subclass_info("".join(buf
), d
, dict)
191 def object_repr(self
, obj
: type[dict] | t
.Callable |
type[list] |
None) -> str:
193 return f
'<span class="object">{escape(r)}</span>'
195 def dispatch_repr(self
, obj
: t
.Any
, recursive
: bool) -> str:
197 return f
'<span class="help">{helper!r}</span>'
198 if isinstance(obj
, (int, float, complex)):
199 return f
'<span class="number">{obj!r}</span>'
200 if isinstance(obj
, str) or isinstance(obj
, bytes):
201 return self
.string_repr(obj
)
202 if isinstance(obj
, RegexType
):
203 return self
.regex_repr(obj
)
204 if isinstance(obj
, list):
205 return self
.list_repr(obj
, recursive
)
206 if isinstance(obj
, tuple):
207 return self
.tuple_repr(obj
, recursive
)
208 if isinstance(obj
, set):
209 return self
.set_repr(obj
, recursive
)
210 if isinstance(obj
, frozenset):
211 return self
.frozenset_repr(obj
, recursive
)
212 if isinstance(obj
, dict):
213 return self
.dict_repr(obj
, recursive
)
214 if isinstance(obj
, deque
):
215 return self
.deque_repr(obj
, recursive
)
216 return self
.object_repr(obj
)
218 def fallback_repr(self
) -> str:
220 info
= "".join(format_exception_only(*sys
.exc_info()[:2]))
224 '<span class="brokenrepr">'
225 f
"<broken repr ({escape(info.strip())})></span>"
228 def repr(self
, obj
: object) -> str:
230 for item
in self
._stack
:
234 self
._stack
.append(obj
)
237 return self
.dispatch_repr(obj
, recursive
)
239 return self
.fallback_repr()
243 def dump_object(self
, obj
: object) -> str:
245 items
: list[tuple[str, str]] |
None = None
247 if isinstance(obj
, dict):
248 title
= "Contents of"
250 for key
, value
in obj
.items():
251 if not isinstance(key
, str):
254 items
.append((key
, self
.repr(value
)))
257 repr = self
.repr(obj
)
260 items
.append((key
, self
.repr(getattr(obj
, key
))))
263 title
= "Details for"
264 title
+= f
" {object.__repr__(obj)[1:-1]}"
265 return self
.render_object_dump(items
, title
, repr)
267 def dump_locals(self
, d
: dict[str, t
.Any
]) -> str:
268 items
= [(key
, self
.repr(value
)) for key
, value
in d
.items()]
269 return self
.render_object_dump(items
, "Local variables in frame")
271 def render_object_dump(
272 self
, items
: list[tuple[str, str]], title
: str, repr: str |
None = None
275 for key
, value
in items
:
276 html_items
.append(f
"<tr><th>{escape(key)}<td><pre class=repr>{value}</pre>")
278 html_items
.append("<tr><td><em>Nothing</em>")
279 return OBJECT_DUMP_HTML
% {
280 "title": escape(title
),
281 "repr": f
"<pre class=repr>{repr if repr else ''}</pre>",
282 "items": "\n".join(html_items
),