1 """Tests for the Formatters.""" 2 3 from math import pi 4 5 try: 6 import numpy 7 except: 8 numpy = None 9 import pytest 10 11 from IPython import get_ipython 12 from traitlets.config import Config 13 from IPython.core.formatters import ( 14 PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key, 15 DisplayFormatter, JSONFormatter, 16 ) 17 from IPython.utils.io import capture_output 18 19 class A(object): 20 def __repr__(self): 21 return 'A()' 22 23 class B(A): 24 def __repr__(self): 25 return 'B()' 26 27 class C: 28 pass 29 30 class BadRepr(object): 31 def __repr__(self): 32 raise ValueError("bad repr") 33 34 class BadPretty(object): 35 _repr_pretty_ = None 36 37 class GoodPretty(object): 38 def _repr_pretty_(self, pp, cycle): 39 pp.text('foo') 40 41 def __repr__(self): 42 return 'GoodPretty()' 43 44 def foo_printer(obj, pp, cycle): 45 pp.text('foo') 46 47 def test_pretty(): 48 f = PlainTextFormatter() 49 f.for_type(A, foo_printer) 50 assert f(A()) == "foo" 51 assert f(B()) == "B()" 52 assert f(GoodPretty()) == "foo" 53 # Just don't raise an exception for the following: 54 f(BadPretty()) 55 56 f.pprint = False 57 assert f(A()) == "A()" 58 assert f(B()) == "B()" 59 assert f(GoodPretty()) == "GoodPretty()" 60 61 62 def test_deferred(): 63 f = PlainTextFormatter() 64 65 def test_precision(): 66 """test various values for float_precision.""" 67 f = PlainTextFormatter() 68 assert f(pi) == repr(pi) 69 f.float_precision = 0 70 if numpy: 71 po = numpy.get_printoptions() 72 assert po["precision"] == 0 73 assert f(pi) == "3" 74 f.float_precision = 2 75 if numpy: 76 po = numpy.get_printoptions() 77 assert po["precision"] == 2 78 assert f(pi) == "3.14" 79 f.float_precision = "%g" 80 if numpy: 81 po = numpy.get_printoptions() 82 assert po["precision"] == 2 83 assert f(pi) == "3.14159" 84 f.float_precision = "%e" 85 assert f(pi) == "3.141593e+00" 86 f.float_precision = "" 87 if numpy: 88 po = numpy.get_printoptions() 89 assert po["precision"] == 8 90 assert f(pi) == repr(pi) 91 92 93 def test_bad_precision(): 94 """test various invalid values for float_precision.""" 95 f = PlainTextFormatter() 96 def set_fp(p): 97 f.float_precision = p 98 99 pytest.raises(ValueError, set_fp, "%") 100 pytest.raises(ValueError, set_fp, "%.3f%i") 101 pytest.raises(ValueError, set_fp, "foo") 102 pytest.raises(ValueError, set_fp, -1) 103 104 def test_for_type(): 105 f = PlainTextFormatter() 106 107 # initial return, None 108 assert f.for_type(C, foo_printer) is None 109 # no func queries 110 assert f.for_type(C) is foo_printer 111 # shouldn't change anything 112 assert f.for_type(C) is foo_printer 113 # None should do the same 114 assert f.for_type(C, None) is foo_printer 115 assert f.for_type(C, None) is foo_printer 116 117 def test_for_type_string(): 118 f = PlainTextFormatter() 119 120 type_str = '%s.%s' % (C.__module__, 'C') 121 122 # initial return, None 123 assert f.for_type(type_str, foo_printer) is None 124 # no func queries 125 assert f.for_type(type_str) is foo_printer 126 assert _mod_name_key(C) in f.deferred_printers 127 assert f.for_type(C) is foo_printer 128 assert _mod_name_key(C) not in f.deferred_printers 129 assert C in f.type_printers 130 131 def test_for_type_by_name(): 132 f = PlainTextFormatter() 133 134 mod = C.__module__ 135 136 # initial return, None 137 assert f.for_type_by_name(mod, "C", foo_printer) is None 138 # no func queries 139 assert f.for_type_by_name(mod, "C") is foo_printer 140 # shouldn't change anything 141 assert f.for_type_by_name(mod, "C") is foo_printer 142 # None should do the same 143 assert f.for_type_by_name(mod, "C", None) is foo_printer 144 assert f.for_type_by_name(mod, "C", None) is foo_printer 145 146 147 def test_lookup(): 148 f = PlainTextFormatter() 149 150 f.for_type(C, foo_printer) 151 assert f.lookup(C()) is foo_printer 152 with pytest.raises(KeyError): 153 f.lookup(A()) 154 155 def test_lookup_string(): 156 f = PlainTextFormatter() 157 type_str = '%s.%s' % (C.__module__, 'C') 158 159 f.for_type(type_str, foo_printer) 160 assert f.lookup(C()) is foo_printer 161 # should move from deferred to imported dict 162 assert _mod_name_key(C) not in f.deferred_printers 163 assert C in f.type_printers 164 165 def test_lookup_by_type(): 166 f = PlainTextFormatter() 167 f.for_type(C, foo_printer) 168 assert f.lookup_by_type(C) is foo_printer 169 with pytest.raises(KeyError): 170 f.lookup_by_type(A) 171 172 def test_lookup_by_type_string(): 173 f = PlainTextFormatter() 174 type_str = '%s.%s' % (C.__module__, 'C') 175 f.for_type(type_str, foo_printer) 176 177 # verify insertion 178 assert _mod_name_key(C) in f.deferred_printers 179 assert C not in f.type_printers 180 181 assert f.lookup_by_type(type_str) is foo_printer 182 # lookup by string doesn't cause import 183 assert _mod_name_key(C) in f.deferred_printers 184 assert C not in f.type_printers 185 186 assert f.lookup_by_type(C) is foo_printer 187 # should move from deferred to imported dict 188 assert _mod_name_key(C) not in f.deferred_printers 189 assert C in f.type_printers 190 191 def test_in_formatter(): 192 f = PlainTextFormatter() 193 f.for_type(C, foo_printer) 194 type_str = '%s.%s' % (C.__module__, 'C') 195 assert C in f 196 assert type_str in f 197 198 def test_string_in_formatter(): 199 f = PlainTextFormatter() 200 type_str = '%s.%s' % (C.__module__, 'C') 201 f.for_type(type_str, foo_printer) 202 assert type_str in f 203 assert C in f 204 205 def test_pop(): 206 f = PlainTextFormatter() 207 f.for_type(C, foo_printer) 208 assert f.lookup_by_type(C) is foo_printer 209 assert f.pop(C, None) is foo_printer 210 f.for_type(C, foo_printer) 211 assert f.pop(C) is foo_printer 212 with pytest.raises(KeyError): 213 f.lookup_by_type(C) 214 with pytest.raises(KeyError): 215 f.pop(C) 216 with pytest.raises(KeyError): 217 f.pop(A) 218 assert f.pop(A, None) is None 219 220 def test_pop_string(): 221 f = PlainTextFormatter() 222 type_str = '%s.%s' % (C.__module__, 'C') 223 224 with pytest.raises(KeyError): 225 f.pop(type_str) 226 227 f.for_type(type_str, foo_printer) 228 f.pop(type_str) 229 with pytest.raises(KeyError): 230 f.lookup_by_type(C) 231 with pytest.raises(KeyError): 232 f.pop(type_str) 233 234 f.for_type(C, foo_printer) 235 assert f.pop(type_str, None) is foo_printer 236 with pytest.raises(KeyError): 237 f.lookup_by_type(C) 238 with pytest.raises(KeyError): 239 f.pop(type_str) 240 assert f.pop(type_str, None) is None 241 242 243 def test_error_method(): 244 f = HTMLFormatter() 245 class BadHTML(object): 246 def _repr_html_(self): 247 raise ValueError("Bad HTML") 248 bad = BadHTML() 249 with capture_output() as captured: 250 result = f(bad) 251 assert result is None 252 assert "Traceback" in captured.stdout 253 assert "Bad HTML" in captured.stdout 254 assert "_repr_html_" in captured.stdout 255 256 def test_nowarn_notimplemented(): 257 f = HTMLFormatter() 258 class HTMLNotImplemented(object): 259 def _repr_html_(self): 260 raise NotImplementedError 261 h = HTMLNotImplemented() 262 with capture_output() as captured: 263 result = f(h) 264 assert result is None 265 assert "" == captured.stderr 266 assert "" == captured.stdout 267 268 269 def test_warn_error_for_type(): 270 f = HTMLFormatter() 271 f.for_type(int, lambda i: name_error) 272 with capture_output() as captured: 273 result = f(5) 274 assert result is None 275 assert "Traceback" in captured.stdout 276 assert "NameError" in captured.stdout 277 assert "name_error" in captured.stdout 278 279 def test_error_pretty_method(): 280 f = PlainTextFormatter() 281 class BadPretty(object): 282 def _repr_pretty_(self): 283 return "hello" 284 bad = BadPretty() 285 with capture_output() as captured: 286 result = f(bad) 287 assert result is None 288 assert "Traceback" in captured.stdout 289 assert "_repr_pretty_" in captured.stdout 290 assert "given" in captured.stdout 291 assert "argument" in captured.stdout 292 293 294 def test_bad_repr_traceback(): 295 f = PlainTextFormatter() 296 bad = BadRepr() 297 with capture_output() as captured: 298 result = f(bad) 299 # catches error, returns None 300 assert result is None 301 assert "Traceback" in captured.stdout 302 assert "__repr__" in captured.stdout 303 assert "ValueError" in captured.stdout 304 305 306 class MakePDF(object): 307 def _repr_pdf_(self): 308 return 'PDF' 309 310 def test_pdf_formatter(): 311 pdf = MakePDF() 312 f = PDFFormatter() 313 assert f(pdf) == "PDF" 314 315 316 def test_print_method_bound(): 317 f = HTMLFormatter() 318 class MyHTML(object): 319 def _repr_html_(self): 320 return "hello" 321 with capture_output() as captured: 322 result = f(MyHTML) 323 assert result is None 324 assert "FormatterWarning" not in captured.stderr 325 326 with capture_output() as captured: 327 result = f(MyHTML()) 328 assert result == "hello" 329 assert captured.stderr == "" 330 331 332 def test_print_method_weird(): 333 334 class TextMagicHat(object): 335 def __getattr__(self, key): 336 return key 337 338 f = HTMLFormatter() 339 340 text_hat = TextMagicHat() 341 assert text_hat._repr_html_ == "_repr_html_" 342 with capture_output() as captured: 343 result = f(text_hat) 344 345 assert result is None 346 assert "FormatterWarning" not in captured.stderr 347 348 class CallableMagicHat(object): 349 def __getattr__(self, key): 350 return lambda : key 351 352 call_hat = CallableMagicHat() 353 with capture_output() as captured: 354 result = f(call_hat) 355 356 assert result is None 357 358 class BadReprArgs(object): 359 def _repr_html_(self, extra, args): 360 return "html" 361 362 bad = BadReprArgs() 363 with capture_output() as captured: 364 result = f(bad) 365 366 assert result is None 367 assert "FormatterWarning" not in captured.stderr 368 369 370 def test_format_config(): 371 """config objects don't pretend to support fancy reprs with lazy attrs""" 372 f = HTMLFormatter() 373 cfg = Config() 374 with capture_output() as captured: 375 result = f(cfg) 376 assert result is None 377 assert captured.stderr == "" 378 379 with capture_output() as captured: 380 result = f(Config) 381 assert result is None 382 assert captured.stderr == "" 383 384 385 def test_pretty_max_seq_length(): 386 f = PlainTextFormatter(max_seq_length=1) 387 lis = list(range(3)) 388 text = f(lis) 389 assert text == "[0, ...]" 390 f.max_seq_length = 0 391 text = f(lis) 392 assert text == "[0, 1, 2]" 393 text = f(list(range(1024))) 394 lines = text.splitlines() 395 assert len(lines) == 1024 396 397 398 def test_ipython_display_formatter(): 399 """Objects with _ipython_display_ defined bypass other formatters""" 400 f = get_ipython().display_formatter 401 catcher = [] 402 class SelfDisplaying(object): 403 def _ipython_display_(self): 404 catcher.append(self) 405 406 class NotSelfDisplaying(object): 407 def __repr__(self): 408 return "NotSelfDisplaying" 409 410 def _ipython_display_(self): 411 raise NotImplementedError 412 413 save_enabled = f.ipython_display_formatter.enabled 414 f.ipython_display_formatter.enabled = True 415 416 yes = SelfDisplaying() 417 no = NotSelfDisplaying() 418 419 d, md = f.format(no) 420 assert d == {"text/plain": repr(no)} 421 assert md == {} 422 assert catcher == [] 423 424 d, md = f.format(yes) 425 assert d == {} 426 assert md == {} 427 assert catcher == [yes] 428 429 f.ipython_display_formatter.enabled = save_enabled 430 431 432 def test_repr_mime(): 433 class HasReprMime(object): 434 def _repr_mimebundle_(self, include=None, exclude=None): 435 return { 436 'application/json+test.v2': { 437 'x': 'y' 438 }, 439 'plain/text' : '<HasReprMime>', 440 'image/png' : 'i-overwrite' 441 } 442 443 def _repr_png_(self): 444 return 'should-be-overwritten' 445 def _repr_html_(self): 446 return '<b>hi!</b>' 447 448 f = get_ipython().display_formatter 449 html_f = f.formatters['text/html'] 450 save_enabled = html_f.enabled 451 html_f.enabled = True 452 obj = HasReprMime() 453 d, md = f.format(obj) 454 html_f.enabled = save_enabled 455 456 assert sorted(d) == [ 457 "application/json+test.v2", 458 "image/png", 459 "plain/text", 460 "text/html", 461 "text/plain", 462 ] 463 assert md == {} 464 465 d, md = f.format(obj, include={"image/png"}) 466 assert list(d.keys()) == [ 467 "image/png" 468 ], "Include should filter out even things from repr_mimebundle" 469 470 assert d["image/png"] == "i-overwrite", "_repr_mimebundle_ take precedence" 471 472 473 def test_pass_correct_include_exclude(): 474 class Tester(object): 475 476 def __init__(self, include=None, exclude=None): 477 self.include = include 478 self.exclude = exclude 479 480 def _repr_mimebundle_(self, include, exclude, **kwargs): 481 if include and (include != self.include): 482 raise ValueError('include got modified: display() may be broken.') 483 if exclude and (exclude != self.exclude): 484 raise ValueError('exclude got modified: display() may be broken.') 485 486 return None 487 488 include = {'a', 'b', 'c'} 489 exclude = {'c', 'e' , 'f'} 490 491 f = get_ipython().display_formatter 492 f.format(Tester(include=include, exclude=exclude), include=include, exclude=exclude) 493 f.format(Tester(exclude=exclude), exclude=exclude) 494 f.format(Tester(include=include), include=include) 495 496 497 def test_repr_mime_meta(): 498 class HasReprMimeMeta(object): 499 def _repr_mimebundle_(self, include=None, exclude=None): 500 data = { 501 'image/png': 'base64-image-data', 502 } 503 metadata = { 504 'image/png': { 505 'width': 5, 506 'height': 10, 507 } 508 } 509 return (data, metadata) 510 511 f = get_ipython().display_formatter 512 obj = HasReprMimeMeta() 513 d, md = f.format(obj) 514 assert sorted(d) == ["image/png", "text/plain"] 515 assert md == { 516 "image/png": { 517 "width": 5, 518 "height": 10, 519 } 520 } 521 522 523 def test_repr_mime_failure(): 524 class BadReprMime(object): 525 def _repr_mimebundle_(self, include=None, exclude=None): 526 raise RuntimeError 527 528 f = get_ipython().display_formatter 529 obj = BadReprMime() 530 d, md = f.format(obj) 531 assert "text/plain" in d 532 533 534 def test_custom_repr_namedtuple_partialmethod(): 535 from functools import partialmethod 536 from typing import NamedTuple 537 538 class Foo(NamedTuple): ... 539 540 Foo.__repr__ = partialmethod(lambda obj: "Hello World") 541 foo = Foo() 542 543 f = PlainTextFormatter() 544 assert f.pprint 545 assert f(foo) == "Hello World" 546