saferepr.py 6.5 KB

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