repr.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
  11. :license: BSD.
  12. """
  13. import sys
  14. import re
  15. import codecs
  16. from traceback import format_exception_only
  17. try:
  18. from collections import deque
  19. except ImportError: # pragma: no cover
  20. deque = None
  21. from werkzeug.utils import escape
  22. from werkzeug._compat import iteritems, PY2, text_type, integer_types, \
  23. string_types
  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):
  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. if deque is not None:
  114. deque_repr = _sequence_repr_maker('<span class="module">collections.'
  115. '</span>deque([', '])', deque)
  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. a = repr(obj[:limit])
  131. b = repr(obj[limit:])
  132. if isinstance(obj, text_type) and PY2:
  133. buf.append('u')
  134. a = a[1:]
  135. b = b[1:]
  136. if b != "''":
  137. buf.extend((escape(a[:-1]), '<span class="extended">', escape(b[1:]), '</span>'))
  138. else:
  139. buf.append(escape(a))
  140. buf.append('</span>')
  141. return _add_subclass_info(u''.join(buf), obj, (bytes, text_type))
  142. def dict_repr(self, d, recursive, limit=5):
  143. if recursive:
  144. return _add_subclass_info(u'{...}', d, dict)
  145. buf = ['{']
  146. have_extended_section = False
  147. for idx, (key, value) in enumerate(iteritems(d)):
  148. if idx:
  149. buf.append(', ')
  150. if idx == limit - 1:
  151. buf.append('<span class="extended">')
  152. have_extended_section = True
  153. buf.append('<span class="pair"><span class="key">%s</span>: '
  154. '<span class="value">%s</span></span>' %
  155. (self.repr(key), self.repr(value)))
  156. if have_extended_section:
  157. buf.append('</span>')
  158. buf.append('}')
  159. return _add_subclass_info(u''.join(buf), d, dict)
  160. def object_repr(self, obj):
  161. r = repr(obj)
  162. if PY2:
  163. r = r.decode('utf-8', 'replace')
  164. return u'<span class="object">%s</span>' % escape(r)
  165. def dispatch_repr(self, obj, recursive):
  166. if obj is helper:
  167. return u'<span class="help">%r</span>' % helper
  168. if isinstance(obj, (integer_types, float, complex)):
  169. return u'<span class="number">%r</span>' % obj
  170. if isinstance(obj, string_types):
  171. return self.string_repr(obj)
  172. if isinstance(obj, RegexType):
  173. return self.regex_repr(obj)
  174. if isinstance(obj, list):
  175. return self.list_repr(obj, recursive)
  176. if isinstance(obj, tuple):
  177. return self.tuple_repr(obj, recursive)
  178. if isinstance(obj, set):
  179. return self.set_repr(obj, recursive)
  180. if isinstance(obj, frozenset):
  181. return self.frozenset_repr(obj, recursive)
  182. if isinstance(obj, dict):
  183. return self.dict_repr(obj, recursive)
  184. if deque is not None and isinstance(obj, deque):
  185. return self.deque_repr(obj, recursive)
  186. return self.object_repr(obj)
  187. def fallback_repr(self):
  188. try:
  189. info = ''.join(format_exception_only(*sys.exc_info()[:2]))
  190. except Exception: # pragma: no cover
  191. info = '?'
  192. if PY2:
  193. info = info.decode('utf-8', 'ignore')
  194. return u'<span class="brokenrepr">&lt;broken repr (%s)&gt;' \
  195. u'</span>' % escape(info.strip())
  196. def repr(self, obj):
  197. recursive = False
  198. for item in self._stack:
  199. if item is obj:
  200. recursive = True
  201. break
  202. self._stack.append(obj)
  203. try:
  204. try:
  205. return self.dispatch_repr(obj, recursive)
  206. except Exception:
  207. return self.fallback_repr()
  208. finally:
  209. self._stack.pop()
  210. def dump_object(self, obj):
  211. repr = items = None
  212. if isinstance(obj, dict):
  213. title = 'Contents of'
  214. items = []
  215. for key, value in iteritems(obj):
  216. if not isinstance(key, string_types):
  217. items = None
  218. break
  219. items.append((key, self.repr(value)))
  220. if items is None:
  221. items = []
  222. repr = self.repr(obj)
  223. for key in dir(obj):
  224. try:
  225. items.append((key, self.repr(getattr(obj, key))))
  226. except Exception:
  227. pass
  228. title = 'Details for'
  229. title += ' ' + object.__repr__(obj)[1:-1]
  230. return self.render_object_dump(items, title, repr)
  231. def dump_locals(self, d):
  232. items = [(key, self.repr(value)) for key, value in d.items()]
  233. return self.render_object_dump(items, 'Local variables in frame')
  234. def render_object_dump(self, items, title, repr=None):
  235. html_items = []
  236. for key, value in items:
  237. html_items.append('<tr><th>%s<td><pre class=repr>%s</pre>' %
  238. (escape(key), value))
  239. if not html_items:
  240. html_items.append('<tr><td><em>Nothing</em>')
  241. return OBJECT_DUMP_HTML % {
  242. 'title': escape(title),
  243. 'repr': repr and '<pre class=repr>%s</pre>' % repr or '',
  244. 'items': '\n'.join(html_items)
  245. }