diff --git a/IPython/core/formatters.py b/IPython/core/formatters.py index adef488..f6298e5 100644 --- a/IPython/core/formatters.py +++ b/IPython/core/formatters.py @@ -53,12 +53,17 @@ class DisplayFormatter(Configurable): formatter.enabled = True else: formatter.enabled = False - + ipython_display_formatter = ForwardDeclaredInstance('FormatterABC') @default('ipython_display_formatter') def _default_formatter(self): return IPythonDisplayFormatter(parent=self) - + + mimebundle_formatter = ForwardDeclaredInstance('FormatterABC') + @default('mimebundle_formatter') + def _default_mime_formatter(self): + return MimeBundleFormatter(parent=self) + # A dict of formatter whose keys are format types (MIME types) and whose # values are subclasses of BaseFormatter. formatters = Dict() @@ -133,8 +138,13 @@ class DisplayFormatter(Configurable): if self.ipython_display_formatter(obj): # object handled itself, don't proceed return {}, {} - + + format_dict, md_dict = self.mimebundle_formatter(obj) + for format_type, formatter in self.formatters.items(): + if format_type in format_dict: + # already got it from mimebundle, don't render again + continue if include and format_type not in include: continue if exclude and format_type in exclude: @@ -849,7 +859,7 @@ class PDFFormatter(BaseFormatter): _return_type = (bytes, unicode_type) class IPythonDisplayFormatter(BaseFormatter): - """A Formatter for objects that know how to display themselves. + """An escape-hatch Formatter for objects that know how to display themselves. To define the callables that compute the representation of your objects, define a :meth:`_ipython_display_` method or use the :meth:`for_type` @@ -859,10 +869,16 @@ class IPythonDisplayFormatter(BaseFormatter): This display formatter has highest priority. If it fires, no other display formatter will be called. + + Prior to IPython 6.1, `_ipython_display_` was the only way to display custom mime-types + without registering a new Formatter. + + IPython 6.1 introduces `_repr_mimebundle_` for displaying custom mime-types, + so `_ipython_display_` should only be used for objects that require unusual + display patterns, such as multiple display calls. """ print_method = ObjectName('_ipython_display_') _return_type = (type(None), bool) - @catch_format_error def __call__(self, obj): @@ -883,6 +899,34 @@ class IPythonDisplayFormatter(BaseFormatter): return True +class MimeBundleFormatter(BaseFormatter): + """A Formatter for arbitrary mime-types. + + Unlike other `_repr__` methods, + `_repr_mimebundle_` should return mime-bundle data, + either the mime-keyed `data` dictionary or the tuple `(data, metadata)`. + Any mime-type is valid. + + To define the callables that compute the mime-bundle representation of your + objects, define a :meth:`_repr_mimebundle_` method or use the :meth:`for_type` + or :meth:`for_type_by_name` methods to register functions that handle + this. + + .. versionadded:: 6.1 + """ + print_method = ObjectName('_repr_mimebundle_') + _return_type = dict + + def _check_return(self, r, obj): + r = super(MimeBundleFormatter, self)._check_return(r, obj) + # always return (data, metadata): + if r is None: + return {}, {} + if not isinstance(r, tuple): + return r, {} + return r + + FormatterABC.register(BaseFormatter) FormatterABC.register(PlainTextFormatter) FormatterABC.register(HTMLFormatter) @@ -895,6 +939,7 @@ FormatterABC.register(LatexFormatter) FormatterABC.register(JSONFormatter) FormatterABC.register(JavascriptFormatter) FormatterABC.register(IPythonDisplayFormatter) +FormatterABC.register(MimeBundleFormatter) def format_display_data(obj, include=None, exclude=None): diff --git a/IPython/core/tests/test_formatters.py b/IPython/core/tests/test_formatters.py index 2a07e2b..f1c14df 100644 --- a/IPython/core/tests/test_formatters.py +++ b/IPython/core/tests/test_formatters.py @@ -436,4 +436,55 @@ def test_json_as_string_deprecated(): d = f(JSONString()) nt.assert_equal(d, {}) nt.assert_equal(len(w), 1) - \ No newline at end of file + + +def test_repr_mime(): + class HasReprMime(object): + def _repr_mimebundle_(self): + return { + 'application/json+test.v2': { + 'x': 'y' + } + } + + def _repr_html_(self): + return 'hi!' + + f = get_ipython().display_formatter + html_f = f.formatters['text/html'] + save_enabled = html_f.enabled + html_f.enabled = True + obj = HasReprMime() + d, md = f.format(obj) + html_f.enabled = save_enabled + + nt.assert_equal(sorted(d), ['application/json+test.v2', 'text/html', 'text/plain']) + nt.assert_equal(md, {}) + + +def test_repr_mime_meta(): + class HasReprMimeMeta(object): + def _repr_mimebundle_(self): + data = { + 'image/png': 'base64-image-data', + } + metadata = { + 'image/png': { + 'width': 5, + 'height': 10, + } + } + return (data, metadata) + + f = get_ipython().display_formatter + obj = HasReprMimeMeta() + d, md = f.format(obj) + nt.assert_equal(sorted(d), ['image/png', 'text/plain']) + nt.assert_equal(md, { + 'image/png': { + 'width': 5, + 'height': 10, + } + }) + +