Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

register custom DisplayFormatter for table schema #16198

Merged
merged 3 commits into from
May 2, 2017
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
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_'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm beginning to wonder if we should be calling this repr_data_resource, based on the mimetype we ended up at (which includes the schema inside)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea being "table schema" refers to just the schema? Makes sense to me.

_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