saferepr.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. # -*- coding: utf-8 -*-
  2. """Streaming, truncating, non-recursive version of :func:`repr`.
  3. Differences from regular :func:`repr`:
  4. - Sets are represented the Python 3 way: ``{1, 2}`` vs ``set([1, 2])``.
  5. - Unicode strings does not have the ``u'`` prefix, even on Python 2.
  6. - Empty set formatted as ``set()`` (Python 3), not ``set([])`` (Python 2).
  7. - Longs don't have the ``L`` suffix.
  8. Very slow with no limits, super quick with limits.
  9. """
  10. from __future__ import absolute_import, unicode_literals
  11. import sys
  12. from collections import Iterable, Mapping, deque, namedtuple
  13. from decimal import Decimal
  14. from itertools import chain
  15. from numbers import Number
  16. from pprint import _recursion
  17. from kombu.utils.encoding import bytes_to_str
  18. from celery.five import items, text_t
  19. from .text import truncate, truncate_bytes
  20. __all__ = ['saferepr', 'reprstream']
  21. # pylint: disable=redefined-outer-name
  22. # We cache globals and attribute lookups, so disable this warning.
  23. IS_PY3 = sys.version_info[0] == 3
  24. if IS_PY3: # pragma: no cover
  25. range_t = (range, )
  26. else:
  27. class range_t(object): # noqa
  28. pass
  29. _literal = namedtuple('_literal', ('value', 'truncate', 'direction'))
  30. _key = namedtuple('_key', ('value',))
  31. _quoted = namedtuple('_quoted', ('value',))
  32. _dirty = namedtuple('_dirty', ('objid',))
  33. chars_t = (bytes, text_t)
  34. safe_t = (Number,)
  35. set_t = (frozenset, set)
  36. LIT_DICT_START = _literal('{', False, +1)
  37. LIT_DICT_KVSEP = _literal(': ', True, 0)
  38. LIT_DICT_END = _literal('}', False, -1)
  39. LIT_LIST_START = _literal('[', False, +1)
  40. LIT_LIST_END = _literal(']', False, -1)
  41. LIT_LIST_SEP = _literal(', ', True, 0)
  42. LIT_SET_START = _literal('{', False, +1)
  43. LIT_SET_END = _literal('}', False, -1)
  44. LIT_TUPLE_START = _literal('(', False, +1)
  45. LIT_TUPLE_END = _literal(')', False, -1)
  46. LIT_TUPLE_END_SV = _literal(',)', False, -1)
  47. def saferepr(o, maxlen=None, maxlevels=3, seen=None):
  48. """Safe version of :func:`repr`.
  49. Warning:
  50. Make sure you set the maxlen argument, or it will be very slow
  51. for recursive objects. With the maxlen set, it's often faster
  52. than built-in repr.
  53. """
  54. return ''.join(_saferepr(
  55. o, maxlen=maxlen, maxlevels=maxlevels, seen=seen
  56. ))
  57. def _chaindict(mapping,
  58. LIT_DICT_KVSEP=LIT_DICT_KVSEP,
  59. LIT_LIST_SEP=LIT_LIST_SEP):
  60. size = len(mapping)
  61. for i, (k, v) in enumerate(items(mapping)):
  62. yield _key(k)
  63. yield LIT_DICT_KVSEP
  64. yield v
  65. if i < (size - 1):
  66. yield LIT_LIST_SEP
  67. def _chainlist(it, LIT_LIST_SEP=LIT_LIST_SEP):
  68. size = len(it)
  69. for i, v in enumerate(it):
  70. yield v
  71. if i < (size - 1):
  72. yield LIT_LIST_SEP
  73. def _repr_empty_set(s):
  74. return '%s()' % (type(s).__name__,)
  75. def _saferepr(o, maxlen=None, maxlevels=3, seen=None):
  76. stack = deque([iter([o])])
  77. for token, it in reprstream(stack, seen=seen, maxlevels=maxlevels):
  78. if maxlen is not None and maxlen <= 0:
  79. yield ', ...'
  80. # move rest back to stack, so that we can include
  81. # dangling parens.
  82. stack.append(it)
  83. break
  84. if isinstance(token, _literal):
  85. val = token.value
  86. elif isinstance(token, _key):
  87. val = saferepr(token.value, maxlen, maxlevels)
  88. elif isinstance(token, _quoted):
  89. val = token.value
  90. if IS_PY3 and isinstance(val, bytes): # pragma: no cover
  91. val = "b'%s'" % (bytes_to_str(truncate_bytes(val, maxlen)),)
  92. else:
  93. val = "'%s'" % (truncate(val, maxlen),)
  94. else:
  95. val = truncate(token, maxlen)
  96. yield val
  97. if maxlen is not None:
  98. maxlen -= len(val)
  99. for rest1 in stack:
  100. # maxlen exceeded, process any dangling parens.
  101. for rest2 in rest1:
  102. if isinstance(rest2, _literal) and not rest2.truncate:
  103. yield rest2.value
  104. def _reprseq(val, lit_start, lit_end, builtin_type, chainer):
  105. if type(val) is builtin_type: # noqa
  106. return lit_start, lit_end, chainer(val)
  107. return (
  108. _literal('%s(%s' % (type(val).__name__, lit_start.value), False, +1),
  109. _literal('%s)' % (lit_end.value,), False, -1),
  110. chainer(val)
  111. )
  112. def reprstream(stack, seen=None, maxlevels=3, level=0, isinstance=isinstance):
  113. """Streaming repr, yielding tokens."""
  114. seen = seen or set()
  115. append = stack.append
  116. popleft = stack.popleft
  117. is_in_seen = seen.__contains__
  118. discard_from_seen = seen.discard
  119. add_to_seen = seen.add
  120. while stack:
  121. lit_start = lit_end = None
  122. it = popleft()
  123. for val in it:
  124. orig = val
  125. if isinstance(val, _dirty):
  126. discard_from_seen(val.objid)
  127. continue
  128. elif isinstance(val, _literal):
  129. level += val.direction
  130. yield val, it
  131. elif isinstance(val, _key):
  132. yield val, it
  133. elif isinstance(val, Decimal):
  134. yield repr(val), it
  135. elif isinstance(val, safe_t):
  136. yield text_t(val), it
  137. elif isinstance(val, chars_t):
  138. yield _quoted(val), it
  139. elif isinstance(val, range_t): # pragma: no cover
  140. yield repr(val), it
  141. else:
  142. if isinstance(val, set_t):
  143. if not val:
  144. yield _repr_empty_set(val), it
  145. continue
  146. lit_start, lit_end, val = _reprseq(
  147. val, LIT_SET_START, LIT_SET_END, set, _chainlist,
  148. )
  149. elif isinstance(val, tuple):
  150. lit_start, lit_end, val = (
  151. LIT_TUPLE_START,
  152. LIT_TUPLE_END_SV if len(val) == 1 else LIT_TUPLE_END,
  153. _chainlist(val))
  154. elif isinstance(val, Mapping):
  155. lit_start, lit_end, val = (
  156. LIT_DICT_START, LIT_DICT_END, _chaindict(val))
  157. elif isinstance(val, Iterable):
  158. lit_start, lit_end, val = (
  159. LIT_LIST_START, LIT_LIST_END, _chainlist(val))
  160. else:
  161. # other type of object
  162. yield repr(val), it
  163. continue
  164. if maxlevels and level >= maxlevels:
  165. yield '%s...%s' % (lit_start.value, lit_end.value), it
  166. continue
  167. objid = id(orig)
  168. if is_in_seen(objid):
  169. yield _recursion(orig), it
  170. continue
  171. add_to_seen(objid)
  172. # Recurse into the new list/tuple/dict/etc by tacking
  173. # the rest of our iterable onto the new it: this way
  174. # it works similar to a linked list.
  175. append(chain([lit_start], val, [_dirty(objid), lit_end], it))
  176. break