Skip to content

Commit 39cc1d0

Browse files
minrkTomAugspurger
authored andcommitted
REF: register custom DisplayFormatter for table schema (#16198)
* register custom DisplayFormatter for table schema instead of using `_ipython_display_` for custom mime-types * remove unused UnserializableWarning * PEP8 fixes
1 parent ef0ad36 commit 39cc1d0

File tree

4 files changed

+53
-88
lines changed

4 files changed

+53
-88
lines changed

pandas/core/config_init.py

+29-10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
module is imported, register them here rather then in the module.
1010
1111
"""
12+
import sys
1213
import warnings
1314

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

342343

343344
def table_schema_cb(key):
344-
# Having _ipython_display_ defined messes with the return value
345-
# from cells, so the Out[x] dictionary breaks.
346-
# Currently table schema is the only thing using it, so we'll
347-
# monkey patch `_ipython_display_` onto NDFrame when config option
348-
# is set
349-
# see https://github.com/pandas-dev/pandas/issues/16168
350-
from pandas.core.generic import NDFrame, _ipython_display_
345+
# first, check if we are in IPython
346+
if 'IPython' not in sys.modules:
347+
# definitely not in IPython
348+
return
349+
from IPython import get_ipython
350+
ip = get_ipython()
351+
if ip is None:
352+
# still not in IPython
353+
return
354+
355+
formatters = ip.display_formatter.formatters
356+
357+
mimetype = "application/vnd.dataresource+json"
351358

352359
if cf.get_option(key):
353-
NDFrame._ipython_display_ = _ipython_display_
354-
elif getattr(NDFrame, '_ipython_display_', None):
355-
del NDFrame._ipython_display_
360+
if mimetype not in formatters:
361+
# define tableschema formatter
362+
from IPython.core.formatters import BaseFormatter
363+
364+
class TableSchemaFormatter(BaseFormatter):
365+
print_method = '_repr_table_schema_'
366+
_return_type = (dict,)
367+
# register it:
368+
formatters[mimetype] = TableSchemaFormatter()
369+
# enable it if it's been disabled:
370+
formatters[mimetype].enabled = True
371+
else:
372+
# unregister tableschema mime-type
373+
if mimetype in formatters:
374+
formatters[mimetype].enabled = False
356375

357376

358377
with cf.config_prefix('display'):

pandas/core/generic.py

-33
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
import pandas.core.algorithms as algos
4444
import pandas.core.common as com
4545
import pandas.core.missing as missing
46-
from pandas.errors import UnserializableWarning
4746
from pandas.io.formats.printing import pprint_thing
4847
from pandas.io.formats.format import format_percentiles
4948
from pandas.tseries.frequencies import to_offset
@@ -6279,38 +6278,6 @@ def logical_func(self, axis=None, bool_only=None, skipna=None, level=None,
62796278
return set_function_name(logical_func, name, cls)
62806279

62816280

6282-
def _ipython_display_(self):
6283-
# Having _ipython_display_ defined messes with the return value
6284-
# from cells, so the Out[x] dictionary breaks.
6285-
# Currently table schema is the only thing using it, so we'll
6286-
# monkey patch `_ipython_display_` onto NDFrame when config option
6287-
# is set
6288-
# see https://github.com/pandas-dev/pandas/issues/16168
6289-
try:
6290-
from IPython.display import display
6291-
except ImportError:
6292-
return None
6293-
6294-
# Series doesn't define _repr_html_ or _repr_latex_
6295-
latex = self._repr_latex_() if hasattr(self, '_repr_latex_') else None
6296-
html = self._repr_html_() if hasattr(self, '_repr_html_') else None
6297-
try:
6298-
table_schema = self._repr_table_schema_()
6299-
except Exception as e:
6300-
warnings.warn("Cannot create table schema representation. "
6301-
"{}".format(e), UnserializableWarning)
6302-
table_schema = None
6303-
# We need the inital newline since we aren't going through the
6304-
# usual __repr__. See
6305-
# https://github.com/pandas-dev/pandas/pull/14904#issuecomment-277829277
6306-
text = "\n" + repr(self)
6307-
6308-
reprs = {"text/plain": text, "text/html": html, "text/latex": latex,
6309-
"application/vnd.dataresource+json": table_schema}
6310-
reprs = {k: v for k, v in reprs.items() if v}
6311-
display(reprs, raw=True)
6312-
6313-
63146281
# install the indexes
63156282
for _name, _indexer in indexing.get_indexers_list():
63166283
NDFrame._create_indexer(_name, _indexer)

pandas/errors/__init__.py

-6
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,3 @@ class ParserWarning(Warning):
5757
"""
5858

5959

60-
class UnserializableWarning(Warning):
61-
"""
62-
Warning that is raised when a DataFrame cannot be serialized.
63-
64-
.. versionadded:: 0.20.0
65-
"""

pandas/tests/io/formats/test_printing.py

+24-39
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import pandas as pd
66

77
from pandas import compat
8-
from pandas.errors import UnserializableWarning
98
import pandas.io.formats.printing as printing
109
import pandas.io.formats.format as fmt
1110
import pandas.util.testing as tm
@@ -137,57 +136,46 @@ def setUpClass(cls):
137136
except ImportError:
138137
pytest.skip("Mock is not installed")
139138
cls.mock = mock
139+
from IPython.core.interactiveshell import InteractiveShell
140+
cls.display_formatter = InteractiveShell.instance().display_formatter
140141

141142
def test_publishes(self):
143+
142144
df = pd.DataFrame({"A": [1, 2]})
143145
objects = [df['A'], df, df] # dataframe / series
144146
expected_keys = [
145147
{'text/plain', 'application/vnd.dataresource+json'},
146148
{'text/plain', 'text/html', 'application/vnd.dataresource+json'},
147149
]
148150

149-
make_patch = self.mock.patch('IPython.display.display')
150151
opt = pd.option_context('display.html.table_schema', True)
151152
for obj, expected in zip(objects, expected_keys):
152-
with opt, make_patch as mock_display:
153-
handle = obj._ipython_display_()
154-
assert mock_display.call_count == 1
155-
assert handle is None
156-
args, kwargs = mock_display.call_args
157-
arg, = args # just one argument
158-
159-
assert kwargs == {"raw": True}
160-
assert set(arg.keys()) == expected
153+
with opt:
154+
formatted = self.display_formatter.format(obj)
155+
assert set(formatted[0].keys()) == expected
161156

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

164-
with opt, with_latex, make_patch as mock_display:
165-
handle = obj._ipython_display_()
166-
args, kwargs = mock_display.call_args
167-
arg, = args
159+
with opt, with_latex:
160+
formatted = self.display_formatter.format(obj)
168161

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

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

179-
make_patch = self.mock.patch('IPython.display.display')
180172
opt = pd.option_context('display.html.table_schema', True)
181-
with opt, make_patch as mock_display:
182-
with pytest.warns(UnserializableWarning) as record:
183-
df._ipython_display_()
184-
args, _ = mock_display.call_args
185-
arg, = args # just one argument
173+
174+
with opt:
175+
formatted = self.display_formatter.format(df)
186176

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

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

200+
formatters = self.display_formatter.formatters
201+
mimetype = 'application/vnd.dataresource+json'
202+
212203
with pd.option_context('display.html.table_schema', True):
213-
assert hasattr(df, '_ipython_display_')
214-
# smoke test that it works
215-
df._ipython_display_()
216-
assert hasattr(df['A'], '_ipython_display_')
217-
df['A']._ipython_display_()
204+
assert 'application/vnd.dataresource+json' in formatters
205+
assert formatters[mimetype].enabled
218206

219-
assert not hasattr(df, '_ipython_display_')
220-
assert not hasattr(df['A'], '_ipython_display_')
221-
# re-unsetting is OK
222-
assert not hasattr(df, '_ipython_display_')
223-
assert not hasattr(df['A'], '_ipython_display_')
207+
# still there, just disabled
208+
assert 'application/vnd.dataresource+json' in formatters
209+
assert not formatters[mimetype].enabled
224210

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

233218

234219
# TODO: fix this broken test

0 commit comments

Comments
 (0)