test_formatters.py
546 lines
| 14.2 KiB
| text/x-python
|
PythonLexer
|
r13977 | """Tests for the Formatters.""" | ||
|
r3209 | |||
|
r3350 | from math import pi | ||
try: | ||||
import numpy | ||||
except: | ||||
numpy = None | ||||
|
r26894 | import pytest | ||
|
r3209 | |||
|
r19557 | from IPython import get_ipython | ||
|
r21253 | from traitlets.config import Config | ||
|
r15121 | from IPython.core.formatters import ( | ||
|
r19384 | PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key, | ||
|
r19557 | DisplayFormatter, JSONFormatter, | ||
|
r15121 | ) | ||
|
r13977 | from IPython.utils.io import capture_output | ||
|
r3209 | |||
class A(object): | ||||
def __repr__(self): | ||||
return 'A()' | ||||
class B(A): | ||||
def __repr__(self): | ||||
return 'B()' | ||||
|
r13781 | class C: | ||
pass | ||||
|
r18025 | class BadRepr(object): | ||
def __repr__(self): | ||||
raise ValueError("bad repr") | ||||
|
r6268 | class BadPretty(object): | ||
_repr_pretty_ = None | ||||
class GoodPretty(object): | ||||
def _repr_pretty_(self, pp, cycle): | ||||
pp.text('foo') | ||||
def __repr__(self): | ||||
return 'GoodPretty()' | ||||
|
r3209 | def foo_printer(obj, pp, cycle): | ||
pp.text('foo') | ||||
def test_pretty(): | ||||
|
r3350 | f = PlainTextFormatter() | ||
|
r3209 | f.for_type(A, foo_printer) | ||
|
r26707 | assert f(A()) == "foo" | ||
assert f(B()) == "B()" | ||||
assert f(GoodPretty()) == "foo" | ||||
|
r6268 | # Just don't raise an exception for the following: | ||
f(BadPretty()) | ||||
|
r3209 | f.pprint = False | ||
|
r26707 | assert f(A()) == "A()" | ||
assert f(B()) == "B()" | ||||
assert f(GoodPretty()) == "GoodPretty()" | ||||
|
r6268 | |||
|
r3209 | |||
def test_deferred(): | ||||
|
r3350 | f = PlainTextFormatter() | ||
def test_precision(): | ||||
"""test various values for float_precision.""" | ||||
f = PlainTextFormatter() | ||||
|
r26707 | assert f(pi) == repr(pi) | ||
|
r3350 | f.float_precision = 0 | ||
if numpy: | ||||
po = numpy.get_printoptions() | ||||
|
r26707 | assert po["precision"] == 0 | ||
assert f(pi) == "3" | ||||
|
r3350 | f.float_precision = 2 | ||
if numpy: | ||||
po = numpy.get_printoptions() | ||||
|
r26707 | assert po["precision"] == 2 | ||
assert f(pi) == "3.14" | ||||
f.float_precision = "%g" | ||||
|
r3350 | if numpy: | ||
po = numpy.get_printoptions() | ||||
|
r26707 | assert po["precision"] == 2 | ||
assert f(pi) == "3.14159" | ||||
f.float_precision = "%e" | ||||
assert f(pi) == "3.141593e+00" | ||||
f.float_precision = "" | ||||
|
r3350 | if numpy: | ||
po = numpy.get_printoptions() | ||||
|
r26707 | assert po["precision"] == 8 | ||
assert f(pi) == repr(pi) | ||||
|
r3350 | |||
def test_bad_precision(): | ||||
"""test various invalid values for float_precision.""" | ||||
f = PlainTextFormatter() | ||||
def set_fp(p): | ||||
|
r26894 | f.float_precision = p | ||
pytest.raises(ValueError, set_fp, "%") | ||||
pytest.raises(ValueError, set_fp, "%.3f%i") | ||||
pytest.raises(ValueError, set_fp, "foo") | ||||
pytest.raises(ValueError, set_fp, -1) | ||||
|
r3350 | |||
|
r13655 | def test_for_type(): | ||
f = PlainTextFormatter() | ||||
|
r13783 | # initial return, None | ||
|
r26894 | assert f.for_type(C, foo_printer) is None | ||
|
r13655 | # no func queries | ||
|
r26894 | assert f.for_type(C) is foo_printer | ||
|
r13655 | # shouldn't change anything | ||
|
r26894 | assert f.for_type(C) is foo_printer | ||
|
r13781 | # None should do the same | ||
|
r26894 | assert f.for_type(C, None) is foo_printer | ||
assert f.for_type(C, None) is foo_printer | ||||
|
r13655 | |||
|
r13781 | def test_for_type_string(): | ||
|
r13655 | f = PlainTextFormatter() | ||
|
r13781 | type_str = '%s.%s' % (C.__module__, 'C') | ||
|
r13783 | # initial return, None | ||
|
r26894 | assert f.for_type(type_str, foo_printer) is None | ||
|
r13781 | # no func queries | ||
|
r26894 | assert f.for_type(type_str) is foo_printer | ||
assert _mod_name_key(C) in f.deferred_printers | ||||
assert f.for_type(C) is foo_printer | ||||
assert _mod_name_key(C) not in f.deferred_printers | ||||
assert C in f.type_printers | ||||
|
r13781 | |||
def test_for_type_by_name(): | ||||
f = PlainTextFormatter() | ||||
mod = C.__module__ | ||||
|
r13655 | |||
|
r13783 | # initial return, None | ||
|
r26894 | assert f.for_type_by_name(mod, "C", foo_printer) is None | ||
|
r13655 | # no func queries | ||
|
r26894 | assert f.for_type_by_name(mod, "C") is foo_printer | ||
|
r13655 | # shouldn't change anything | ||
|
r26894 | assert f.for_type_by_name(mod, "C") is foo_printer | ||
|
r13781 | # None should do the same | ||
|
r26894 | assert f.for_type_by_name(mod, "C", None) is foo_printer | ||
assert f.for_type_by_name(mod, "C", None) is foo_printer | ||||
|
r13655 | |||
|
r13781 | def test_lookup(): | ||
f = PlainTextFormatter() | ||||
|
r13783 | f.for_type(C, foo_printer) | ||
|
r26894 | assert f.lookup(C()) is foo_printer | ||
with pytest.raises(KeyError): | ||||
|
r13783 | f.lookup(A()) | ||
def test_lookup_string(): | ||||
f = PlainTextFormatter() | ||||
type_str = '%s.%s' % (C.__module__, 'C') | ||||
f.for_type(type_str, foo_printer) | ||||
|
r26894 | assert f.lookup(C()) is foo_printer | ||
|
r13783 | # should move from deferred to imported dict | ||
|
r26894 | assert _mod_name_key(C) not in f.deferred_printers | ||
assert C in f.type_printers | ||||
|
r13783 | |||
def test_lookup_by_type(): | ||||
f = PlainTextFormatter() | ||||
f.for_type(C, foo_printer) | ||||
|
r26894 | assert f.lookup_by_type(C) is foo_printer | ||
with pytest.raises(KeyError): | ||||
|
r13783 | f.lookup_by_type(A) | ||
def test_lookup_by_type_string(): | ||||
f = PlainTextFormatter() | ||||
type_str = '%s.%s' % (C.__module__, 'C') | ||||
f.for_type(type_str, foo_printer) | ||||
# verify insertion | ||||
|
r26894 | assert _mod_name_key(C) in f.deferred_printers | ||
assert C not in f.type_printers | ||||
|
r13781 | |||
|
r26894 | assert f.lookup_by_type(type_str) is foo_printer | ||
|
r13788 | # lookup by string doesn't cause import | ||
|
r26894 | assert _mod_name_key(C) in f.deferred_printers | ||
assert C not in f.type_printers | ||||
|
r13788 | |||
|
r26894 | assert f.lookup_by_type(C) is foo_printer | ||
|
r13783 | # should move from deferred to imported dict | ||
|
r26894 | assert _mod_name_key(C) not in f.deferred_printers | ||
assert C in f.type_printers | ||||
|
r13783 | |||
|
r13788 | def test_in_formatter(): | ||
f = PlainTextFormatter() | ||||
f.for_type(C, foo_printer) | ||||
type_str = '%s.%s' % (C.__module__, 'C') | ||||
|
r26894 | assert C in f | ||
assert type_str in f | ||||
|
r13788 | |||
def test_string_in_formatter(): | ||||
f = PlainTextFormatter() | ||||
type_str = '%s.%s' % (C.__module__, 'C') | ||||
f.for_type(type_str, foo_printer) | ||||
|
r26894 | assert type_str in f | ||
assert C in f | ||||
|
r13788 | |||
|
r13783 | def test_pop(): | ||
f = PlainTextFormatter() | ||||
f.for_type(C, foo_printer) | ||||
|
r26894 | assert f.lookup_by_type(C) is foo_printer | ||
assert f.pop(C, None) is foo_printer | ||||
|
r13788 | f.for_type(C, foo_printer) | ||
|
r26894 | assert f.pop(C) is foo_printer | ||
with pytest.raises(KeyError): | ||||
|
r13783 | f.lookup_by_type(C) | ||
|
r26894 | with pytest.raises(KeyError): | ||
|
r13783 | f.pop(C) | ||
|
r26894 | with pytest.raises(KeyError): | ||
|
r13783 | f.pop(A) | ||
|
r26894 | assert f.pop(A, None) is None | ||
|
r13783 | |||
def test_pop_string(): | ||||
f = PlainTextFormatter() | ||||
|
r13781 | type_str = '%s.%s' % (C.__module__, 'C') | ||
|
r26894 | with pytest.raises(KeyError): | ||
|
r13783 | f.pop(type_str) | ||
f.for_type(type_str, foo_printer) | ||||
f.pop(type_str) | ||||
|
r26894 | with pytest.raises(KeyError): | ||
|
r13783 | f.lookup_by_type(C) | ||
|
r26894 | with pytest.raises(KeyError): | ||
|
r13783 | f.pop(type_str) | ||
f.for_type(C, foo_printer) | ||||
|
r26894 | assert f.pop(type_str, None) is foo_printer | ||
with pytest.raises(KeyError): | ||||
|
r13783 | f.lookup_by_type(C) | ||
|
r26894 | with pytest.raises(KeyError): | ||
|
r13783 | f.pop(type_str) | ||
|
r26894 | assert f.pop(type_str, None) is None | ||
|
r13788 | |||
|
r13783 | |||
|
r18025 | def test_error_method(): | ||
|
r13977 | f = HTMLFormatter() | ||
class BadHTML(object): | ||||
def _repr_html_(self): | ||||
|
r18025 | raise ValueError("Bad HTML") | ||
|
r13977 | bad = BadHTML() | ||
with capture_output() as captured: | ||||
result = f(bad) | ||||
|
r26894 | assert result is None | ||
assert "Traceback" in captured.stdout | ||||
assert "Bad HTML" in captured.stdout | ||||
assert "_repr_html_" in captured.stdout | ||||
|
r13977 | |||
|
r14636 | def test_nowarn_notimplemented(): | ||
f = HTMLFormatter() | ||||
class HTMLNotImplemented(object): | ||||
def _repr_html_(self): | ||||
raise NotImplementedError | ||||
h = HTMLNotImplemented() | ||||
with capture_output() as captured: | ||||
result = f(h) | ||||
|
r26894 | assert result is None | ||
|
r26707 | assert "" == captured.stderr | ||
assert "" == captured.stdout | ||||
|
r14636 | |||
|
r13977 | def test_warn_error_for_type(): | ||
f = HTMLFormatter() | ||||
f.for_type(int, lambda i: name_error) | ||||
with capture_output() as captured: | ||||
result = f(5) | ||||
|
r26894 | assert result is None | ||
assert "Traceback" in captured.stdout | ||||
assert "NameError" in captured.stdout | ||||
assert "name_error" in captured.stdout | ||||
|
r13977 | |||
|
r18025 | def test_error_pretty_method(): | ||
|
r13977 | f = PlainTextFormatter() | ||
class BadPretty(object): | ||||
def _repr_pretty_(self): | ||||
return "hello" | ||||
bad = BadPretty() | ||||
with capture_output() as captured: | ||||
result = f(bad) | ||||
|
r26894 | assert result is None | ||
assert "Traceback" in captured.stdout | ||||
assert "_repr_pretty_" in captured.stdout | ||||
assert "given" in captured.stdout | ||||
assert "argument" in captured.stdout | ||||
|
r18025 | |||
def test_bad_repr_traceback(): | ||||
f = PlainTextFormatter() | ||||
bad = BadRepr() | ||||
with capture_output() as captured: | ||||
result = f(bad) | ||||
# catches error, returns None | ||||
|
r26894 | assert result is None | ||
assert "Traceback" in captured.stdout | ||||
assert "__repr__" in captured.stdout | ||||
assert "ValueError" in captured.stdout | ||||
|
r18025 | |||
|
r13977 | |||
|
r15121 | class MakePDF(object): | ||
def _repr_pdf_(self): | ||||
return 'PDF' | ||||
|
r13655 | |||
|
r15121 | def test_pdf_formatter(): | ||
pdf = MakePDF() | ||||
f = PDFFormatter() | ||||
|
r26707 | assert f(pdf) == "PDF" | ||
|
r15216 | |||
def test_print_method_bound(): | ||||
f = HTMLFormatter() | ||||
class MyHTML(object): | ||||
def _repr_html_(self): | ||||
return "hello" | ||||
with capture_output() as captured: | ||||
result = f(MyHTML) | ||||
|
r26894 | assert result is None | ||
assert "FormatterWarning" not in captured.stderr | ||||
|
r15216 | |||
with capture_output() as captured: | ||||
result = f(MyHTML()) | ||||
|
r26707 | assert result == "hello" | ||
assert captured.stderr == "" | ||||
|
r15216 | |||
|
r19026 | def test_print_method_weird(): | ||
class TextMagicHat(object): | ||||
def __getattr__(self, key): | ||||
return key | ||||
f = HTMLFormatter() | ||||
|
r26707 | |||
|
r19026 | text_hat = TextMagicHat() | ||
|
r26707 | assert text_hat._repr_html_ == "_repr_html_" | ||
|
r19026 | with capture_output() as captured: | ||
result = f(text_hat) | ||||
|
r26894 | assert result is None | ||
assert "FormatterWarning" not in captured.stderr | ||||
|
r19026 | |||
class CallableMagicHat(object): | ||||
def __getattr__(self, key): | ||||
return lambda : key | ||||
call_hat = CallableMagicHat() | ||||
with capture_output() as captured: | ||||
result = f(call_hat) | ||||
|
r26707 | |||
|
r26733 | assert result is None | ||
|
r19026 | |||
class BadReprArgs(object): | ||||
def _repr_html_(self, extra, args): | ||||
return "html" | ||||
bad = BadReprArgs() | ||||
with capture_output() as captured: | ||||
result = f(bad) | ||||
|
r26894 | assert result is None | ||
assert "FormatterWarning" not in captured.stderr | ||||
|
r19026 | |||
|
r15216 | def test_format_config(): | ||
"""config objects don't pretend to support fancy reprs with lazy attrs""" | ||||
f = HTMLFormatter() | ||||
cfg = Config() | ||||
with capture_output() as captured: | ||||
result = f(cfg) | ||||
|
r26894 | assert result is None | ||
|
r26707 | assert captured.stderr == "" | ||
|
r15216 | |||
with capture_output() as captured: | ||||
result = f(Config) | ||||
|
r26894 | assert result is None | ||
|
r26707 | assert captured.stderr == "" | ||
|
r18518 | |||
def test_pretty_max_seq_length(): | ||||
f = PlainTextFormatter(max_seq_length=1) | ||||
lis = list(range(3)) | ||||
text = f(lis) | ||||
|
r26707 | assert text == "[0, ...]" | ||
|
r18518 | f.max_seq_length = 0 | ||
text = f(lis) | ||||
|
r26707 | assert text == "[0, 1, 2]" | ||
|
r18518 | text = f(list(range(1024))) | ||
lines = text.splitlines() | ||||
|
r26707 | assert len(lines) == 1024 | ||
|
r19384 | |||
def test_ipython_display_formatter(): | ||||
"""Objects with _ipython_display_ defined bypass other formatters""" | ||||
f = get_ipython().display_formatter | ||||
catcher = [] | ||||
class SelfDisplaying(object): | ||||
def _ipython_display_(self): | ||||
catcher.append(self) | ||||
class NotSelfDisplaying(object): | ||||
def __repr__(self): | ||||
return "NotSelfDisplaying" | ||||
def _ipython_display_(self): | ||||
raise NotImplementedError | ||||
|
r23310 | save_enabled = f.ipython_display_formatter.enabled | ||
f.ipython_display_formatter.enabled = True | ||||
|
r19384 | yes = SelfDisplaying() | ||
no = NotSelfDisplaying() | ||||
|
r26707 | |||
|
r19384 | d, md = f.format(no) | ||
|
r26707 | assert d == {"text/plain": repr(no)} | ||
assert md == {} | ||||
assert catcher == [] | ||||
|
r19384 | d, md = f.format(yes) | ||
|
r26707 | assert d == {} | ||
assert md == {} | ||||
assert catcher == [yes] | ||||
|
r19384 | |||
|
r23310 | f.ipython_display_formatter.enabled = save_enabled | ||
|
r23578 | def test_repr_mime(): | ||
class HasReprMime(object): | ||||
|
r23618 | def _repr_mimebundle_(self, include=None, exclude=None): | ||
|
r23578 | return { | ||
'application/json+test.v2': { | ||||
'x': 'y' | ||||
|
r23612 | }, | ||
'plain/text' : '<HasReprMime>', | ||||
'image/png' : 'i-overwrite' | ||||
|
r23578 | } | ||
|
r23612 | |||
def _repr_png_(self): | ||||
return 'should-be-overwritten' | ||||
|
r23578 | def _repr_html_(self): | ||
return '<b>hi!</b>' | ||||
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 | ||||
|
r26707 | assert sorted(d) == [ | ||
"application/json+test.v2", | ||||
"image/png", | ||||
"plain/text", | ||||
"text/html", | ||||
"text/plain", | ||||
] | ||||
assert md == {} | ||||
|
r23612 | |||
|
r26707 | d, md = f.format(obj, include={"image/png"}) | ||
assert list(d.keys()) == [ | ||||
"image/png" | ||||
], "Include should filter out even things from repr_mimebundle" | ||||
assert d["image/png"] == "i-overwrite", "_repr_mimebundle_ take precedence" | ||||
|
r23578 | |||
|
r23674 | |||
def test_pass_correct_include_exclude(): | ||||
class Tester(object): | ||||
def __init__(self, include=None, exclude=None): | ||||
self.include = include | ||||
self.exclude = exclude | ||||
def _repr_mimebundle_(self, include, exclude, **kwargs): | ||||
if include and (include != self.include): | ||||
raise ValueError('include got modified: display() may be broken.') | ||||
if exclude and (exclude != self.exclude): | ||||
raise ValueError('exclude got modified: display() may be broken.') | ||||
return None | ||||
include = {'a', 'b', 'c'} | ||||
exclude = {'c', 'e' , 'f'} | ||||
f = get_ipython().display_formatter | ||||
f.format(Tester(include=include, exclude=exclude), include=include, exclude=exclude) | ||||
f.format(Tester(exclude=exclude), exclude=exclude) | ||||
f.format(Tester(include=include), include=include) | ||||
|
r23578 | def test_repr_mime_meta(): | ||
class HasReprMimeMeta(object): | ||||
|
r23618 | def _repr_mimebundle_(self, include=None, exclude=None): | ||
|
r23578 | 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) | ||||
|
r26707 | assert sorted(d) == ["image/png", "text/plain"] | ||
assert md == { | ||||
"image/png": { | ||||
"width": 5, | ||||
"height": 10, | ||||
|
r23578 | } | ||
|
r26707 | } | ||
|
r24042 | |||
def test_repr_mime_failure(): | ||||
class BadReprMime(object): | ||||
def _repr_mimebundle_(self, include=None, exclude=None): | ||||
raise RuntimeError | ||||
f = get_ipython().display_formatter | ||||
obj = BadReprMime() | ||||
d, md = f.format(obj) | ||||
|
r26707 | assert "text/plain" in d | ||
|
r28791 | |||
def test_custom_repr_namedtuple_partialmethod(): | ||||
from functools import partialmethod | ||||
from typing import NamedTuple | ||||
class Foo(NamedTuple): | ||||
... | ||||
Foo.__repr__ = partialmethod(lambda obj: "Hello World") | ||||
foo = Foo() | ||||
f = PlainTextFormatter() | ||||
assert f.pprint | ||||
assert f(foo) == "Hello World" | ||||