Sfoglia il codice sorgente

saferepr: Handle any exception in __repr__

Ask Solem 8 anni fa
parent
commit
0988db799d
2 ha cambiato i file con 50 aggiunte e 3 eliminazioni
  1. 44 3
      celery/utils/saferepr.py
  2. 6 0
      t/unit/utils/test_saferepr.py

+ 44 - 3
celery/utils/saferepr.py

@@ -13,6 +13,7 @@ Very slow with no limits, super quick with limits.
 from __future__ import absolute_import, unicode_literals
 
 import sys
+import traceback
 
 from collections import deque, namedtuple
 
@@ -38,13 +39,33 @@ else:
     class range_t(object):  # noqa
         pass
 
+#: Node representing literal text.
+#:   - .value: is the literal text value
+#:   - .truncate: specifies if this text can be truncated, for things like
+#:                LIT_DICT_END this will be False, as we always display
+#:                the ending brackets, e.g:  [[[1, 2, 3, ...,], ..., ]]
+#:   - .direction: If +1 the current level is increment by one,
+#:                 if -1 the current level is decremented by one, and
+#:                 if 0 the current level is unchanged.
 _literal = namedtuple('_literal', ('value', 'truncate', 'direction'))
+
+#: Node representing a dictionary key.
 _key = namedtuple('_key', ('value',))
+
+#: Node representing quoted text, e.g. a string value.
 _quoted = namedtuple('_quoted', ('value',))
+
+
+#: Recursion protection.
 _dirty = namedtuple('_dirty', ('objid',))
 
+#: Types that are repsented as chars.
 chars_t = (bytes, text_t)
+
+#: Types that are regarded as safe to call repr on.
 safe_t = (Number,)
+
+#: Set types.
 set_t = (frozenset, set)
 
 LIT_DICT_START = _literal('{', False, +1)
@@ -61,6 +82,7 @@ LIT_TUPLE_END_SV = _literal(',)', False, -1)
 
 
 def saferepr(o, maxlen=None, maxlevels=3, seen=None):
+    # type: (Any, int, int, Set) -> str
     """Safe version of :func:`repr`.
 
     Warning:
@@ -76,6 +98,7 @@ def saferepr(o, maxlen=None, maxlevels=3, seen=None):
 def _chaindict(mapping,
                LIT_DICT_KVSEP=LIT_DICT_KVSEP,
                LIT_LIST_SEP=LIT_LIST_SEP):
+    # type: (Dict, _literal, _literal) -> Iterator[Any]
     size = len(mapping)
     for i, (k, v) in enumerate(items(mapping)):
         yield _key(k)
@@ -86,6 +109,7 @@ def _chaindict(mapping,
 
 
 def _chainlist(it, LIT_LIST_SEP=LIT_LIST_SEP):
+    # type: (List) -> Iterator[Any]
     size = len(it)
     for i, v in enumerate(it):
         yield v
@@ -94,10 +118,12 @@ def _chainlist(it, LIT_LIST_SEP=LIT_LIST_SEP):
 
 
 def _repr_empty_set(s):
+    # type: (Set) -> str
     return '%s()' % (type(s).__name__,)
 
 
 def _safetext(val):
+    # type: (AnyStr) -> str
     if isinstance(val, bytes):
         try:
             val.encode('utf-8')
@@ -109,6 +135,7 @@ def _safetext(val):
 
 
 def _format_binary_bytes(val, maxlen, ellipsis='...'):
+    # type: (bytes, int, str) -> str
     if maxlen and len(val) > maxlen:
         # we don't want to copy all the data, just take what we need.
         chunk = memoryview(val)[:maxlen].tobytes()
@@ -117,6 +144,7 @@ def _format_binary_bytes(val, maxlen, ellipsis='...'):
 
 
 def _repr_binary_bytes(val):
+    # type: (bytes) -> str
     try:
         return val.decode('utf-8')
     except UnicodeDecodeError:
@@ -132,13 +160,24 @@ def _repr_binary_bytes(val):
 
 
 def _format_chars(val, maxlen):
+    # type: (AnyStr, int) -> str
     if IS_PY3 and isinstance(val, bytes):  # pragma: no cover
         return _format_binary_bytes(val, maxlen)
     else:
         return "'{0}'".format(truncate(val, maxlen))
 
 
+def _repr(obj):
+    # type: (Any) -> str
+    try:
+        return repr(obj)
+    except Exception as exc:
+        return '<Unrepresentable {0!r}{1:#x}: {2!r} {3!r}>'.format(
+            type(obj), id(obj), exc, '\n'.join(traceback.format_stack()))
+
+
 def _saferepr(o, maxlen=None, maxlevels=3, seen=None):
+    # type: (Any, int, int, Set) -> str
     stack = deque([iter([o])])
     for token, it in reprstream(stack, seen=seen, maxlevels=maxlevels):
         if maxlen is not None and maxlen <= 0:
@@ -166,6 +205,7 @@ def _saferepr(o, maxlen=None, maxlevels=3, seen=None):
 
 
 def _reprseq(val, lit_start, lit_end, builtin_type, chainer):
+    # type: (Sequence, _literal, _literal, Any, Any) -> Tuple[Any, ...]
     if type(val) is builtin_type:  # noqa
         return lit_start, lit_end, chainer(val)
     return (
@@ -177,6 +217,7 @@ def _reprseq(val, lit_start, lit_end, builtin_type, chainer):
 
 def reprstream(stack, seen=None, maxlevels=3, level=0, isinstance=isinstance):
     """Streaming repr, yielding tokens."""
+    # type: (deque, Set, int, int, Callable) -> Iterator[Any]
     seen = seen or set()
     append = stack.append
     popleft = stack.popleft
@@ -198,13 +239,13 @@ def reprstream(stack, seen=None, maxlevels=3, level=0, isinstance=isinstance):
             elif isinstance(val, _key):
                 yield val, it
             elif isinstance(val, Decimal):
-                yield repr(val), it
+                yield _repr(val), it
             elif isinstance(val, safe_t):
                 yield text_t(val), it
             elif isinstance(val, chars_t):
                 yield _quoted(val), it
             elif isinstance(val, range_t):  # pragma: no cover
-                yield repr(val), it
+                yield _repr(val), it
             else:
                 if isinstance(val, set_t):
                     if not val:
@@ -226,7 +267,7 @@ def reprstream(stack, seen=None, maxlevels=3, level=0, isinstance=isinstance):
                         LIT_LIST_START, LIT_LIST_END, _chainlist(val))
                 else:
                     # other type of object
-                    yield repr(val), it
+                    yield _repr(val), it
                     continue
 
                 if maxlevels and level >= maxlevels:

+ 6 - 0
t/unit/utils/test_saferepr.py

@@ -222,3 +222,9 @@ class test_saferepr:
             assert result.endswith("...'")
         else:  # Python 3.4
             assert result
+
+    def test_repr_raises(self):
+        class O(object):
+            def __repr__(self):
+                raise KeyError('foo')
+        assert 'Unrepresentable' in saferepr(O())