From 49af4ae97202ea1b8001d7f308ec9bbf3db0e16a 2013-11-04 23:16:36 From: Thomas Kluyver Date: 2013-11-04 23:16:36 Subject: [PATCH] Merge pull request #4459 from minrk/bad-repr protect against broken repr in lib.pretty --- diff --git a/IPython/core/formatters.py b/IPython/core/formatters.py index 195664c..01a8637 100644 --- a/IPython/core/formatters.py +++ b/IPython/core/formatters.py @@ -202,7 +202,7 @@ class FormatterABC(with_metaclass(abc.ABCMeta, object)): """ try: return repr(obj) - except TypeError: + except Exception: return None @@ -465,10 +465,7 @@ class PlainTextFormatter(BaseFormatter): def __call__(self, obj): """Compute the pretty representation of the object.""" if not self.pprint: - try: - return repr(obj) - except TypeError: - return '' + return pretty._safe_repr(obj) else: # This uses use StringIO, as cStringIO doesn't handle unicode. stream = StringIO() diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py index 20cf5b8..2da3dd8 100644 --- a/IPython/lib/pretty.py +++ b/IPython/lib/pretty.py @@ -125,6 +125,60 @@ __all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter', _re_pattern_type = type(re.compile('')) +def _failed_repr(obj, e): + """Render a failed repr, including the exception. + + Tries to get exception and type info + """ + # get exception name + if e.__class__.__module__ in ('exceptions', 'builtins'): + ename = e.__class__.__name__ + else: + ename = '{}.{}'.format( + e.__class__.__module__, + e.__class__.__name__, + ) + # and exception string, which sometimes fails + # (usually due to unicode error message) + try: + estr = str(e) + except Exception: + estr = "unknown" + + # and class name + try: + klass = _safe_getattr(obj, '__class__', None) or type(obj) + mod = _safe_getattr(klass, '__module__', None) + if mod in (None, '__builtin__', 'builtins', 'exceptions'): + classname = klass.__name__ + else: + classname = mod + '.' + klass.__name__ + except Exception: + # this may be paranoid, but we already know repr is broken + classname = "unknown type" + + # the informative repr + return ") failed: {}: {}>".format( + classname, id(obj), ename, estr, + ) + +def _safe_repr(obj): + """Don't assume repr is not broken.""" + try: + return repr(obj) + except Exception as e: + return _failed_repr(obj, e) + +def _safe_getattr(obj, attr, default=None): + """Safe version of getattr. + + Same as getattr, but will return ``default`` on any Exception, + rather than raising. + """ + try: + return getattr(obj, attr, default) + except Exception: + return default def pretty(obj, verbose=False, max_width=79, newline='\n'): """ @@ -346,7 +400,7 @@ class RepresentationPrinter(PrettyPrinter): self.stack.append(obj_id) self.begin_group() try: - obj_class = getattr(obj, '__class__', None) or type(obj) + obj_class = _safe_getattr(obj, '__class__', None) or type(obj) # First try to find registered singleton printers for the type. try: printer = self.singleton_pprinters[obj_id] @@ -388,8 +442,8 @@ class RepresentationPrinter(PrettyPrinter): class is not in the registry. Successful matches will be moved to the regular type registry for future use. """ - mod = getattr(cls, '__module__', None) - name = getattr(cls, '__name__', None) + mod = _safe_getattr(cls, '__module__', None) + name = _safe_getattr(cls, '__name__', None) key = (mod, name) printer = None if key in self.deferred_pprinters: @@ -492,10 +546,10 @@ def _default_pprint(obj, p, cycle): The default print function. Used if an object does not provide one and it's none of the builtin objects. """ - klass = getattr(obj, '__class__', None) or type(obj) - if getattr(klass, '__repr__', None) not in _baseclass_reprs: + klass = _safe_getattr(obj, '__class__', None) or type(obj) + if _safe_getattr(klass, '__repr__', None) not in _baseclass_reprs: # A user-provided repr. Find newlines and replace them with p.break_() - output = repr(obj) + output = _safe_repr(obj) for idx,output_line in enumerate(output.splitlines()): if idx: p.break_() @@ -658,7 +712,7 @@ def _re_pattern_pprint(obj, p, cycle): def _type_pprint(obj, p, cycle): """The pprint for classes and types.""" - mod = getattr(obj, '__module__', None) + mod = _safe_getattr(obj, '__module__', None) if mod is None: # Heap allocated types might not have the module attribute, # and others may set it to None. @@ -673,7 +727,7 @@ def _type_pprint(obj, p, cycle): def _repr_pprint(obj, p, cycle): """A pprint that just redirects to the normal repr function.""" - p.text(repr(obj)) + p.text(_safe_repr(obj)) def _function_pprint(obj, p, cycle): diff --git a/IPython/lib/tests/test_pretty.py b/IPython/lib/tests/test_pretty.py index 2e0aaab..0df3448 100644 --- a/IPython/lib/tests/test_pretty.py +++ b/IPython/lib/tests/test_pretty.py @@ -74,6 +74,10 @@ class BreakingReprParent(object): with p.group(4,"TG: ",":"): p.pretty(BreakingRepr()) +class BadRepr(object): + + def __repr__(self): + return 1/0 def test_indentation(): @@ -150,4 +154,31 @@ def test_pprint_break_repr(): """ output = pretty.pretty(BreakingReprParent()) expected = "TG: Breaking(\n ):" - nt.assert_equal(output, expected) \ No newline at end of file + nt.assert_equal(output, expected) + +def test_bad_repr(): + """Don't raise, even when repr fails""" + output = pretty.pretty(BadRepr()) + nt.assert_in("failed", output) + nt.assert_in("at 0x", output) + nt.assert_in("test_pretty", output) + +class BadException(Exception): + def __str__(self): + return -1 + +class ReallyBadRepr(object): + __module__ = 1 + @property + def __class__(self): + raise ValueError("I am horrible") + + def __repr__(self): + raise BadException() + +def test_really_bad_repr(): + output = pretty.pretty(ReallyBadRepr()) + nt.assert_in("failed", output) + nt.assert_in("BadException: unknown", output) + nt.assert_in("unknown type", output) + \ No newline at end of file