repr.py 9.4 KB

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