Skip to content

Commit

Permalink
REF: register custom DisplayFormatter for table schema (pandas-dev#16198
Browse files Browse the repository at this point in the history
)

* register custom DisplayFormatter for table schema

instead of using `_ipython_display_` for custom mime-types

* remove unused UnserializableWarning

* PEP8 fixes
  • Loading branch information
minrk authored and TomAugspurger committed May 2, 2017
1 parent ef0ad36 commit 39cc1d0
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 88 deletions.
39 changes: 29 additions & 10 deletions pandas/core/config_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
module is imported, register them here rather then in the module.
"""
import sys
import warnings

import pandas.core.config as cf
Expand Down Expand Up @@ -341,18 +342,36 @@ def mpl_style_cb(key):


def table_schema_cb(key):
# Having _ipython_display_ defined messes with the return value
# from cells, so the Out[x] dictionary breaks.
# Currently table schema is the only thing using it, so we'll
# monkey patch `_ipython_display_` onto NDFrame when config option
# is set
# see https://github.com/pandas-dev/pandas/issues/16168
from pandas.core.generic import NDFrame, _ipython_display_
# first, check if we are in IPython
if 'IPython' not in sys.modules:
# definitely not in IPython
return
from IPython import get_ipython
ip = get_ipython()
if ip is None:
# still not in IPython
return

formatters = ip.display_formatter.formatters

mimetype = "application/vnd.dataresource+json"

if cf.get_option(key):
NDFrame._ipython_display_ = _ipython_display_
elif getattr(NDFrame, '_ipython_display_', None):
del NDFrame._ipython_display_
if mimetype not in formatters:
# define tableschema formatter
from IPython.core.formatters import BaseFormatter

class TableSchemaFormatter(BaseFormatter):
print_method = '_repr_table_schema_'
_return_type = (dict,)
# register it:
formatters[mimetype] = TableSchemaFormatter()
# enable it if it's been disabled:
formatters[mimetype].enabled = True
else:
# unregister tableschema mime-type
if mimetype in formatters:
formatters[mimetype].enabled = False


with cf.config_prefix('display'):
Expand Down
33 changes: 0 additions & 33 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
import pandas.core.algorithms as algos
import pandas.core.common as com
import pandas.core.missing as missing
from pandas.errors import UnserializableWarning
from pandas.io.formats.printing import pprint_thing
from pandas.io.formats.format import format_percentiles
from pandas.tseries.frequencies import to_offset
Expand Down Expand Up @@ -6279,38 +6278,6 @@ def logical_func(self, axis=None, bool_only=None, skipna=None, level=None,
return set_function_name(logical_func, name, cls)


def _ipython_display_(self):
# Having _ipython_display_ defined messes with the return value
# from cells, so the Out[x] dictionary breaks.
# Currently table schema is the only thing using it, so we'll
# monkey patch `_ipython_display_` onto NDFrame when config option
# is set
# see https://github.com/pandas-dev/pandas/issues/16168
try:
from IPython.display import display
except ImportError:
return None

# Series doesn't define _repr_html_ or _repr_latex_
latex = self._repr_latex_() if hasattr(self, '_repr_latex_') else None
html = self._repr_html_() if hasattr(self, '_repr_html_') else None
try:
table_schema = self._repr_table_schema_()
except Exception as e:
warnings.warn("Cannot create table schema representation. "
"{}".format(e), UnserializableWarning)
table_schema = None
# We need the inital newline since we aren't going through the
# usual __repr__. See
# https://github.com/pandas-dev/pandas/pull/14904#issuecomment-277829277
text = "\n" + repr(self)

reprs = {"text/plain": text, "text/html": html, "text/latex": latex,
"application/vnd.dataresource+json": table_schema}
reprs = {k: v for k, v in reprs.items() if v}
display(reprs, raw=True)


# install the indexes
for _name, _indexer in indexing.get_indexers_list():
NDFrame._create_indexer(_name, _indexer)
6 changes: 0 additions & 6 deletions pandas/errors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,3 @@ class ParserWarning(Warning):
"""


class UnserializableWarning(Warning):
"""
Warning that is raised when a DataFrame cannot be serialized.
.. versionadded:: 0.20.0
"""
63 changes: 24 additions & 39 deletions pandas/tests/io/formats/test_printing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import pandas as pd

from pandas import compat
from pandas.errors import UnserializableWarning
import pandas.io.formats.printing as printing
import pandas.io.formats.format as fmt
import pandas.util.testing as tm
Expand Down Expand Up @@ -137,57 +136,46 @@ def setUpClass(cls):
except ImportError:
pytest.skip("Mock is not installed")
cls.mock = mock
from IPython.core.interactiveshell import InteractiveShell
cls.display_formatter = InteractiveShell.instance().display_formatter

def test_publishes(self):

df = pd.DataFrame({"A": [1, 2]})
objects = [df['A'], df, df] # dataframe / series
expected_keys = [
{'text/plain', 'application/vnd.dataresource+json'},
{'text/plain', 'text/html', 'application/vnd.dataresource+json'},
]

make_patch = self.mock.patch('IPython.display.display')
opt = pd.option_context('display.html.table_schema', True)
for obj, expected in zip(objects, expected_keys):
with opt, make_patch as mock_display:
handle = obj._ipython_display_()
assert mock_display.call_count == 1
assert handle is None
args, kwargs = mock_display.call_args
arg, = args # just one argument

assert kwargs == {"raw": True}
assert set(arg.keys()) == expected
with opt:
formatted = self.display_formatter.format(obj)
assert set(formatted[0].keys()) == expected

with_latex = pd.option_context('display.latex.repr', True)

with opt, with_latex, make_patch as mock_display:
handle = obj._ipython_display_()
args, kwargs = mock_display.call_args
arg, = args
with opt, with_latex:
formatted = self.display_formatter.format(obj)

expected = {'text/plain', 'text/html', 'text/latex',
'application/vnd.dataresource+json'}
assert set(arg.keys()) == expected
assert set(formatted[0].keys()) == expected

def test_publishes_not_implemented(self):
# column MultiIndex
# GH 15996
midx = pd.MultiIndex.from_product([['A', 'B'], ['a', 'b', 'c']])
df = pd.DataFrame(np.random.randn(5, len(midx)), columns=midx)

make_patch = self.mock.patch('IPython.display.display')
opt = pd.option_context('display.html.table_schema', True)
with opt, make_patch as mock_display:
with pytest.warns(UnserializableWarning) as record:
df._ipython_display_()
args, _ = mock_display.call_args
arg, = args # just one argument

with opt:
formatted = self.display_formatter.format(df)

expected = {'text/plain', 'text/html'}
assert set(arg.keys()) == expected
assert "orient='table' is not supported for MultiIndex" in (
record[-1].message.args[0])
assert set(formatted[0].keys()) == expected

def test_config_on(self):
df = pd.DataFrame({"A": [1, 2]})
Expand All @@ -209,26 +197,23 @@ def test_config_monkeypatches(self):
assert not hasattr(df, '_ipython_display_')
assert not hasattr(df['A'], '_ipython_display_')

formatters = self.display_formatter.formatters
mimetype = 'application/vnd.dataresource+json'

with pd.option_context('display.html.table_schema', True):
assert hasattr(df, '_ipython_display_')
# smoke test that it works
df._ipython_display_()
assert hasattr(df['A'], '_ipython_display_')
df['A']._ipython_display_()
assert 'application/vnd.dataresource+json' in formatters
assert formatters[mimetype].enabled

assert not hasattr(df, '_ipython_display_')
assert not hasattr(df['A'], '_ipython_display_')
# re-unsetting is OK
assert not hasattr(df, '_ipython_display_')
assert not hasattr(df['A'], '_ipython_display_')
# still there, just disabled
assert 'application/vnd.dataresource+json' in formatters
assert not formatters[mimetype].enabled

# able to re-set
with pd.option_context('display.html.table_schema', True):
assert hasattr(df, '_ipython_display_')
assert 'application/vnd.dataresource+json' in formatters
assert formatters[mimetype].enabled
# smoke test that it works
df._ipython_display_()
assert hasattr(df['A'], '_ipython_display_')
df['A']._ipython_display_()
self.display_formatter.format(cf)


# TODO: fix this broken test
Expand Down

0 comments on commit 39cc1d0

Please sign in to comment.