repr.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. # -*- coding: utf-8 -*-
  2. """
  3. werkzeug.debug.repr
  4. ~~~~~~~~~~~~~~~~~~~
  5. This module implements object representations for debugging purposes.
  6. Unlike the default repr these reprs expose a lot more information and
  7. produce HTML instead of ASCII.
  8. Together with the CSS and JavaScript files of the debugger this gives
  9. a colorful and more compact output.
  10. :copyright: 2007 Pallets
  11. :license: BSD-3-Clause
  12. """
  13. import codecs
  14. import re
  15. import sys
  16. from collections import deque
  17. from traceback import format_exception_only
  18. from .._compat import integer_types
  19. from .._compat import iteritems
  20. from .._compat import PY2
  21. from .._compat import string_types
  22. from .._compat import text_type
  23. from ..utils import escape
  24. missing = object()
  25. _paragraph_re = re.compile(r"(?:\r\n|\r|\n){2,}")
  26. RegexType = type(_paragraph_re)
  27. HELP_HTML = """\
  28. <div class=box>
  29. <h3>%(title)s</h3>
  30. <pre class=help>%(text)s</pre>
  31. </div>\
  32. """
  33. OBJECT_DUMP_HTML = """\
  34. <div class=box>
  35. <h3>%(title)s</h3>
  36. %(repr)s
  37. <table>%(items)s</table>
  38. </div>\
  39. """
  40. def debug_repr(obj):
  41. """Creates a debug repr of an object as HTML unicode string."""
  42. return DebugReprGenerator().repr(obj)
  43. def dump(obj=missing):
  44. """Print the object details to stdout._write (for the interactive
  45. console of the web debugger.
  46. """
  47. gen = DebugReprGenerator()
  48. if obj is missing:
  49. rv = gen.dump_locals(sys._getframe(1).f_locals)
  50. else:
  51. rv = gen.dump_object(obj)
  52. sys.stdout._write(rv)
  53. class _Helper(object):
  54. """Displays an HTML version of the normal help, for the interactive
  55. debugger only because it requires a patched sys.stdout.
  56. """
  57. def __repr__(self):
  58. return "Type help(object) for help about object."
  59. def __call__(self, topic=None):
  60. if topic is None:
  61. sys.stdout._write("<span class=help>%s</span>" % repr(self))
  62. return
  63. import pydoc
  64. pydoc.help(topic)
  65. rv = sys.stdout.reset()
  66. if isinstance(rv, bytes):
  67. rv = rv.decode("utf-8", "ignore")
  68. paragraphs = _paragraph_re.split(rv)
  69. if len(paragraphs) > 1:
  70. title = paragraphs[0]
  71. text = "\n\n".join(paragraphs[1:])
  72. else: # pragma: no cover
  73. title = "Help"
  74. text = paragraphs[0]
  75. sys.stdout._write(HELP_HTML % {"title": title, "text": text})
  76. helper = _Helper()
  77. def _add_subclass_info(inner, obj, base):
  78. if isinstance(base, tuple):
  79. for base in base:
  80. if type(obj) is base:
  81. return inner
  82. elif type(obj) is base:
  83. return inner
  84. module = ""
  85. if obj.__class__.__module__ not in ("__builtin__", "exceptions"):
  86. module = '<span class="module">%s.</span>' % obj.__class__.__module__
  87. return "%s%s(%s)" % (module, obj.__class__.__name__, inner)
  88. class DebugReprGenerator(object):
  89. def __init__(self):
  90. self._stack = []
  91. def _sequence_repr_maker(left, right, base=object(), limit=8): # noqa: B008, B902
  92. def proxy(self, obj, recursive):
  93. if recursive:
  94. return _add_subclass_info(left + "..." + right, obj, base)
  95. buf = [left]
  96. have_extended_section = False
  97. for idx, item in enumerate(obj):
  98. if idx:
  99. buf.append(", ")
  100. if idx == limit:
  101. buf.append('<span class="extended">')
  102. have_extended_section = True
  103. buf.append(self.repr(item))
  104. if have_extended_section:
  105. buf.append("</span>")
  106. buf.append(right)
  107. return _add_subclass_info(u"".join(buf), obj, base)
  108. return proxy
  109. list_repr = _sequence_repr_maker("[", "]", list)
  110. tuple_repr = _sequence_repr_maker("(", ")", tuple)
  111. set_repr = _sequence_repr_maker("set([", "])", set)
  112. frozenset_repr = _sequence_repr_maker("frozenset([", "])", frozenset)
  113. deque_repr = _sequence_repr_maker(
  114. '<span class="module">collections.' "</span>deque([", "])", deque
  115. )
  116. del _sequence_repr_maker
  117. def regex_repr(self, obj):
  118. pattern = repr(obj.pattern)
  119. if PY2:
  120. pattern = pattern.decode("string-escape", "ignore")
  121. else:
  122. pattern = codecs.decode(pattern, "unicode-escape", "ignore")
  123. if pattern[:1] == "u":
  124. pattern = "ur" + pattern[1:]
  125. else:
  126. pattern = "r" + pattern
  127. return u're.compile(<span class="string regex">%s</span>)' % pattern
  128. def string_repr(self, obj, limit=70):
  129. buf = ['<span class="string">']
  130. r = repr(obj)
  131. # shorten the repr when the hidden part would be at least 3 chars
  132. if len(r) - limit > 2:
  133. buf.extend(
  134. (
  135. escape(r[:limit]),
  136. '<span class="extended">',
  137. escape(r[limit:]),
  138. "</span>",
  139. )
  140. )
  141. else:
  142. buf.append(escape(r))
  143. buf.append("</span>")
  144. out = u"".join(buf)
  145. # if the repr looks like a standard string, add subclass info if needed
  146. if r[0] in "'\"" or (r[0] in "ub" and r[1] in "'\""):
  147. return _add_subclass_info(out, obj, (bytes, text_type))
  148. # otherwise, assume the repr distinguishes the subclass already
  149. return out
  150. def dict_repr(self, d, recursive, limit=5):
  151. if recursive:
  152. return _add_subclass_info(u"{...}", d, dict)
  153. buf = ["{"]
  154. have_extended_section = False
  155. for idx, (key, value) in enumerate(iteritems(d)):
  156. if idx:
  157. buf.append(", ")
  158. if idx == limit - 1:
  159. buf.append('<span class="extended">')
  160. have_extended_section = True
  161. buf.append(
  162. '<span class="pair"><span class="key">%s</span>: '
  163. '<span class="value">%s</span></span>'
  164. % (self.repr(key), self.repr(value))
  165. )
  166. if have_extended_section:
  167. buf.append("</span>")
  168. buf.append("}")
  169. return _add_subclass_info(u"".join(buf), d, dict)
  170. def object_repr(self, obj):
  171. r = repr(obj)
  172. if PY2:
  173. r = r.decode("utf-8", "replace")
  174. return u'<span class="object">%s</span>' % escape(r)
  175. def dispatch_repr(self, obj, recursive):
  176. if obj is helper:
  177. return u'<span class="help">%r</span>' % helper
  178. if isinstance(obj, (integer_types, float, complex)):
  179. return u'<span class="number">%r</span>' % obj
  180. if isinstance(obj, string_types) or isinstance(obj, bytes):
  181. return self.string_repr(obj)
  182. if isinstance(obj, RegexType):
  183. return self.regex_repr(obj)
  184. if isinstance(obj, list):
  185. return self.list_repr(obj, recursive)
  186. if isinstance(obj, tuple):
  187. return self.tuple_repr(obj, recursive)
  188. if isinstance(obj, set):
  189. return self.set_repr(obj, recursive)
  190. if isinstance(obj, frozenset):
  191. return self.frozenset_repr(obj, recursive)
  192. if isinstance(obj, dict):
  193. return self.dict_repr(obj, recursive)
  194. if deque is not None and isinstance(obj, deque):
  195. return self.deque_repr(obj, recursive)
  196. return self.object_repr(obj)
  197. def fallback_repr(self):
  198. try:
  199. info = "".join(format_exception_only(*sys.exc_info()[:2]))
  200. except Exception: # pragma: no cover
  201. info = "?"
  202. if PY2:
  203. info = info.decode("utf-8", "ignore")
  204. return u'<span class="brokenrepr">&lt;broken repr (%s)&gt;' u"</span>" % escape(
  205. info.strip()
  206. )
  207. def repr(self, obj):
  208. recursive = False
  209. for item in self._stack:
  210. if item is obj:
  211. recursive = True
  212. break
  213. self._stack.append(obj)
  214. try:
  215. try:
  216. return self.dispatch_repr(obj, recursive)
  217. except Exception:
  218. return self.fallback_repr()
  219. finally:
  220. self._stack.pop()
  221. def dump_object(self, obj):
  222. repr = items = None
  223. if isinstance(obj, dict):
  224. title = "Contents of"
  225. items = []
  226. for key, value in iteritems(obj):
  227. if not isinstance(key, string_types):
  228. items = None
  229. break
  230. items.append((key, self.repr(value)))
  231. if items is None:
  232. items = []
  233. repr = self.repr(obj)
  234. for key in dir(obj):
  235. try:
  236. items.append((key, self.repr(getattr(obj, key))))
  237. except Exception:
  238. pass
  239. title = "Details for"
  240. title += " " + object.__repr__(obj)[1:-1]
  241. return self.render_object_dump(items, title, repr)
  242. def dump_locals(self, d):
  243. items = [(key, self.repr(value)) for key, value in d.items()]
  244. return self.render_object_dump(items, "Local variables in frame")
  245. def render_object_dump(self, items, title, repr=None):
  246. html_items = []
  247. for key, value in items:
  248. html_items.append(
  249. "<tr><th>%s<td><pre class=repr>%s</pre>" % (escape(key), value)
  250. )
  251. if not html_items:
  252. html_items.append("<tr><td><em>Nothing</em>")
  253. return OBJECT_DUMP_HTML % {
  254. "title": escape(title),
  255. "repr": "<pre class=repr>%s</pre>" % repr if repr else "",
  256. "items": "\n".join(html_items),
  257. }