Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
warn on failed formatter calls
  • Loading branch information
minrk committed Dec 28, 2013
commit 4f2f15bc8d4724bc2f3beb5883aeffc9d3c586d0
50 changes: 31 additions & 19 deletions IPython/core/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@
import sys
import warnings

from IPython.external.decorator import decorator

# Our own imports
from IPython.config.configurable import Configurable
from IPython.lib import pretty
from IPython.utils.traitlets import (
Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
)
from IPython.utils.warn import warn
from IPython.utils.py3compat import (
unicode_to_str, with_metaclass, PY3, string_types,
)
Expand Down Expand Up @@ -180,6 +183,16 @@ def format_types(self):
#-----------------------------------------------------------------------------


@decorator
def warn_format_error(method, self, *args, **kwargs):
"""decorator for warning on failed format call"""
try:
return method(self, *args, **kwargs)
except Exception as e:
warn("Exception in %s formatter: %s" % (self.format_type, e))
return None


class FormatterABC(with_metaclass(abc.ABCMeta, object)):
""" Abstract base class for Formatters.

Expand All @@ -194,17 +207,16 @@ class FormatterABC(with_metaclass(abc.ABCMeta, object)):

# Is the formatter enabled...
enabled = True

@abc.abstractmethod
@warn_format_error
def __call__(self, obj):
"""Return a JSON'able representation of the object.

If the object cannot be formatted by this formatter, then return None
If the object cannot be formatted by this formatter,
warn and return None.
"""
try:
return repr(obj)
except Exception:
return None
return repr(obj)


def _mod_name_key(typ):
Expand All @@ -223,6 +235,7 @@ def _get_type(obj):

_raise_key_error = object()


class BaseFormatter(Configurable):
"""A base formatter class that is configurable.

Expand Down Expand Up @@ -266,24 +279,22 @@ class BaseFormatter(Configurable):
# Map (modulename, classname) pairs to the format functions.
deferred_printers = Dict(config=True)

@warn_format_error
def __call__(self, obj):
"""Compute the format for an object."""
if self.enabled:
# lookup registered printer
try:
# lookup registered printer
try:
printer = self.lookup(obj)
except KeyError:
pass
else:
return printer(obj)
# Finally look for special method names
method = pretty._safe_getattr(obj, self.print_method, None)
if method is not None:
return method()
return None
except Exception:
printer = self.lookup(obj)
except KeyError:
pass
else:
return printer(obj)
# Finally look for special method names
method = pretty._safe_getattr(obj, self.print_method, None)
if method is not None:
return method()
return None
else:
return None

Expand Down Expand Up @@ -599,6 +610,7 @@ def _deferred_printers_default(self):

#### FormatterABC interface ####

@warn_format_error
def __call__(self, obj):
"""Compute the pretty representation of the object."""
if not self.pprint:
Expand Down
42 changes: 39 additions & 3 deletions IPython/core/tests/test_formatters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""Tests for the Formatters.
"""
"""Tests for the Formatters."""

from math import pi

Expand All @@ -9,7 +8,8 @@
numpy = None
import nose.tools as nt

from IPython.core.formatters import PlainTextFormatter, _mod_name_key
from IPython.core.formatters import PlainTextFormatter, HTMLFormatter, _mod_name_key
from IPython.utils.io import capture_output

class A(object):
def __repr__(self):
Expand Down Expand Up @@ -231,4 +231,40 @@ def test_pop_string():
nt.assert_is(f.pop(type_str, None), None)


def test_warn_error_method():
f = HTMLFormatter()
class BadHTML(object):
def _repr_html_(self):
return 1/0
bad = BadHTML()
with capture_output() as captured:
result = f(bad)
nt.assert_is(result, None)
nt.assert_in("WARNING", captured.stderr)
nt.assert_in("text/html", captured.stderr)
nt.assert_in("zero", captured.stderr)

def test_warn_error_for_type():
f = HTMLFormatter()
f.for_type(int, lambda i: name_error)
with capture_output() as captured:
result = f(5)
nt.assert_is(result, None)
nt.assert_in("WARNING", captured.stderr)
nt.assert_in("text/html", captured.stderr)
nt.assert_in("name_error", captured.stderr)

def test_warn_error_pretty_method():
f = PlainTextFormatter()
class BadPretty(object):
def _repr_pretty_(self):
return "hello"
bad = BadPretty()
with capture_output() as captured:
result = f(bad)
nt.assert_is(result, None)
nt.assert_in("WARNING", captured.stderr)
nt.assert_in("text/plain", captured.stderr)
nt.assert_in("argument", captured.stderr)