##// END OF EJS Templates
Merge pull request #10654 from ipython/auto-backport-of-pr-10625...
Matthias Bussonnier -
r23772:12ae1852 merge
parent child Browse files
Show More
@@ -1,1224 +1,1224
1 1 # -*- coding: utf-8 -*-
2 2 """Top-level display functions for displaying object in different formats."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from __future__ import print_function
8 8
9 9 try:
10 10 from base64 import encodebytes as base64_encode
11 11 except ImportError:
12 12 from base64 import encodestring as base64_encode
13 13
14 14 from binascii import b2a_hex
15 15 import json
16 16 import mimetypes
17 17 import os
18 18 import struct
19 19 import sys
20 20 import warnings
21 21
22 22 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
23 23 unicode_type)
24 24 from IPython.testing.skipdoctest import skip_doctest
25 25
26 26 __all__ = ['display', 'display_pretty', 'display_html', 'display_markdown',
27 27 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json',
28 28 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
29 29 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'JSON', 'Javascript',
30 30 'Image', 'clear_output', 'set_matplotlib_formats', 'set_matplotlib_close',
31 31 'publish_display_data', 'update_display', 'DisplayHandle']
32 32
33 33 #-----------------------------------------------------------------------------
34 34 # utility functions
35 35 #-----------------------------------------------------------------------------
36 36
37 37 def _safe_exists(path):
38 38 """Check path, but don't let exceptions raise"""
39 39 try:
40 40 return os.path.exists(path)
41 41 except Exception:
42 42 return False
43 43
44 44 def _merge(d1, d2):
45 45 """Like update, but merges sub-dicts instead of clobbering at the top level.
46 46
47 47 Updates d1 in-place
48 48 """
49 49
50 50 if not isinstance(d2, dict) or not isinstance(d1, dict):
51 51 return d2
52 52 for key, value in d2.items():
53 53 d1[key] = _merge(d1.get(key), value)
54 54 return d1
55 55
56 56 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
57 57 """internal implementation of all display_foo methods
58 58
59 59 Parameters
60 60 ----------
61 61 mimetype : str
62 62 The mimetype to be published (e.g. 'image/png')
63 63 objs : tuple of objects
64 64 The Python objects to display, or if raw=True raw text data to
65 65 display.
66 66 raw : bool
67 67 Are the data objects raw data or Python objects that need to be
68 68 formatted before display? [default: False]
69 69 metadata : dict (optional)
70 70 Metadata to be associated with the specific mimetype output.
71 71 """
72 72 if metadata:
73 73 metadata = {mimetype: metadata}
74 74 if raw:
75 75 # turn list of pngdata into list of { 'image/png': pngdata }
76 76 objs = [ {mimetype: obj} for obj in objs ]
77 77 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
78 78
79 79 #-----------------------------------------------------------------------------
80 80 # Main functions
81 81 #-----------------------------------------------------------------------------
82 82 # use * to indicate transient is keyword-only
83 83 def publish_display_data(data, metadata=None, source=None, **kwargs):
84 84 """Publish data and metadata to all frontends.
85 85
86 86 See the ``display_data`` message in the messaging documentation for
87 87 more details about this message type.
88 88
89 89 The following MIME types are currently implemented:
90 90
91 91 * text/plain
92 92 * text/html
93 93 * text/markdown
94 94 * text/latex
95 95 * application/json
96 96 * application/javascript
97 97 * image/png
98 98 * image/jpeg
99 99 * image/svg+xml
100 100
101 101 Parameters
102 102 ----------
103 103 data : dict
104 104 A dictionary having keys that are valid MIME types (like
105 105 'text/plain' or 'image/svg+xml') and values that are the data for
106 106 that MIME type. The data itself must be a JSON'able data
107 107 structure. Minimally all data should have the 'text/plain' data,
108 108 which can be displayed by all frontends. If more than the plain
109 109 text is given, it is up to the frontend to decide which
110 110 representation to use.
111 111 metadata : dict
112 112 A dictionary for metadata related to the data. This can contain
113 113 arbitrary key, value pairs that frontends can use to interpret
114 114 the data. mime-type keys matching those in data can be used
115 115 to specify metadata about particular representations.
116 116 source : str, deprecated
117 117 Unused.
118 118 transient : dict, keyword-only
119 119 A dictionary of transient data, such as display_id.
120 120 """
121 121 from IPython.core.interactiveshell import InteractiveShell
122 122
123 123 display_pub = InteractiveShell.instance().display_pub
124 124
125 125 # only pass transient if supplied,
126 126 # to avoid errors with older ipykernel.
127 127 # TODO: We could check for ipykernel version and provide a detailed upgrade message.
128 128
129 129 display_pub.publish(
130 130 data=data,
131 131 metadata=metadata,
132 132 **kwargs
133 133 )
134 134
135 135
136 136 def _new_id():
137 137 """Generate a new random text id with urandom"""
138 138 return b2a_hex(os.urandom(16)).decode('ascii')
139 139
140 140
141 141 def display(*objs, **kwargs):
142 142 """Display a Python object in all frontends.
143 143
144 144 By default all representations will be computed and sent to the frontends.
145 145 Frontends can decide which representation is used and how.
146 146
147 147 In terminal IPython this will be similar to using :func:`print`, for use in richer
148 148 frontends see Jupyter notebook examples with rich display logic.
149 149
150 150 Parameters
151 151 ----------
152 152 objs : tuple of objects
153 153 The Python objects to display.
154 154 raw : bool, optional
155 155 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
156 156 or Python objects that need to be formatted before display? [default: False]
157 157 include : list, tuple or set, optional
158 158 A list of format type strings (MIME types) to include in the
159 159 format data dict. If this is set *only* the format types included
160 160 in this list will be computed.
161 161 exclude : list, tuple or set, optional
162 162 A list of format type strings (MIME types) to exclude in the format
163 163 data dict. If this is set all format types will be computed,
164 164 except for those included in this argument.
165 165 metadata : dict, optional
166 166 A dictionary of metadata to associate with the output.
167 167 mime-type keys in this dictionary will be associated with the individual
168 168 representation formats, if they exist.
169 169 transient : dict, optional
170 170 A dictionary of transient data to associate with the output.
171 171 Data in this dict should not be persisted to files (e.g. notebooks).
172 172 display_id : str, bool optional
173 173 Set an id for the display.
174 174 This id can be used for updating this display area later via update_display.
175 175 If given as `True`, generate a new `display_id`
176 176 kwargs: additional keyword-args, optional
177 177 Additional keyword-arguments are passed through to the display publisher.
178 178
179 179 Returns
180 180 -------
181 181
182 182 handle: DisplayHandle
183 183 Returns a handle on updatable displays for use with :func:`update_display`,
184 184 if `display_id` is given. Returns :any:`None` if no `display_id` is given
185 185 (default).
186 186
187 187 Examples
188 188 --------
189 189
190 190 >>> class Json(object):
191 191 ... def __init__(self, json):
192 192 ... self.json = json
193 193 ... def _repr_pretty_(self, pp, cycle):
194 194 ... import json
195 195 ... pp.text(json.dumps(self.json, indent=2))
196 196 ... def __repr__(self):
197 197 ... return str(self.json)
198 198 ...
199 199
200 200 >>> d = Json({1:2, 3: {4:5}})
201 201
202 202 >>> print(d)
203 203 {1: 2, 3: {4: 5}}
204 204
205 205 >>> display(d)
206 206 {
207 207 "1": 2,
208 208 "3": {
209 209 "4": 5
210 210 }
211 211 }
212 212
213 213 >>> def int_formatter(integer, pp, cycle):
214 214 ... pp.text('I'*integer)
215 215
216 216 >>> plain = get_ipython().display_formatter.formatters['text/plain']
217 217 >>> plain.for_type(int, int_formatter)
218 218 <function _repr_pprint at 0x...>
219 219 >>> display(7-5)
220 220 II
221 221
222 222 >>> del plain.type_printers[int]
223 223 >>> display(7-5)
224 224 2
225 225
226 226 See Also
227 227 --------
228 228
229 229 :func:`update_display`
230 230
231 231 Notes
232 232 -----
233 233
234 234 In Python, objects can declare their textual representation using the
235 235 `__repr__` method. IPython expands on this idea and allows objects to declare
236 236 other, rich representations including:
237 237
238 238 - HTML
239 239 - JSON
240 240 - PNG
241 241 - JPEG
242 242 - SVG
243 243 - LaTeX
244 244
245 245 A single object can declare some or all of these representations; all are
246 246 handled by IPython's display system.
247 247
248 248 The main idea of the first approach is that you have to implement special
249 249 display methods when you define your class, one for each representation you
250 250 want to use. Here is a list of the names of the special methods and the
251 251 values they must return:
252 252
253 253 - `_repr_html_`: return raw HTML as a string
254 254 - `_repr_json_`: return a JSONable dict
255 255 - `_repr_jpeg_`: return raw JPEG data
256 256 - `_repr_png_`: return raw PNG data
257 257 - `_repr_svg_`: return raw SVG data as a string
258 258 - `_repr_latex_`: return LaTeX commands in a string surrounded by "$".
259 259 - `_repr_mimebundle_`: return a full mimebundle containing the mapping
260 260 from all mimetypes to data
261 261
262 262 When you are directly writing your own classes, you can adapt them for
263 263 display in IPython by following the above approach. But in practice, you
264 264 often need to work with existing classes that you can't easily modify.
265 265
266 266 You can refer to the documentation on IPython display formatters in order to
267 267 register custom formatters for already existing types.
268 268
269 269 .. versionadded:: 5.4 display available without import
270 270 .. versionadded:: 6.1 display available without import
271 271
272 272 Since IPython 5.4 and 6.1 :func:`display` is automatically made available to
273 273 the user without import. If you are using display in a document that might
274 274 be used in a pure python context or with older version of IPython, use the
275 275 following import at the top of your file::
276 276
277 277 from IPython.display import display
278 278
279 279 """
280 280 raw = kwargs.pop('raw', False)
281 281 include = kwargs.pop('include', None)
282 282 exclude = kwargs.pop('exclude', None)
283 283 metadata = kwargs.pop('metadata', None)
284 284 transient = kwargs.pop('transient', None)
285 285 display_id = kwargs.pop('display_id', None)
286 286 if transient is None:
287 287 transient = {}
288 288 if display_id:
289 289 if display_id is True:
290 290 display_id = _new_id()
291 291 transient['display_id'] = display_id
292 292 if kwargs.get('update') and 'display_id' not in transient:
293 293 raise TypeError('display_id required for update_display')
294 294 if transient:
295 295 kwargs['transient'] = transient
296 296
297 297 from IPython.core.interactiveshell import InteractiveShell
298 298
299 299 if not raw:
300 300 format = InteractiveShell.instance().display_formatter.format
301 301
302 302 for obj in objs:
303 303 if raw:
304 304 publish_display_data(data=obj, metadata=metadata, **kwargs)
305 305 else:
306 306 format_dict, md_dict = format(obj, include=include, exclude=exclude)
307 307 if not format_dict:
308 308 # nothing to display (e.g. _ipython_display_ took over)
309 309 continue
310 310 if metadata:
311 311 # kwarg-specified metadata gets precedence
312 312 _merge(md_dict, metadata)
313 313 publish_display_data(data=format_dict, metadata=md_dict, **kwargs)
314 314 if display_id:
315 315 return DisplayHandle(display_id)
316 316
317 317
318 318 # use * for keyword-only display_id arg
319 319 def update_display(obj, **kwargs):
320 320 """Update an existing display by id
321 321
322 322 Parameters
323 323 ----------
324 324
325 325 obj:
326 326 The object with which to update the display
327 327 display_id: keyword-only
328 328 The id of the display to update
329 329
330 330 See Also
331 331 --------
332 332
333 333 :func:`display`
334 334 """
335 335 sentinel = object()
336 336 display_id = kwargs.pop('display_id', sentinel)
337 337 if display_id is sentinel:
338 338 raise TypeError("update_display() missing 1 required keyword-only argument: 'display_id'")
339 339 kwargs['update'] = True
340 340 display(obj, display_id=display_id, **kwargs)
341 341
342 342
343 343 class DisplayHandle(object):
344 344 """A handle on an updatable display
345 345
346 346 Call `.update(obj)` to display a new object.
347 347
348 348 Call `.display(obj`) to add a new instance of this display,
349 349 and update existing instances.
350 350
351 351 See Also
352 352 --------
353 353
354 354 :func:`display`, :func:`update_display`
355 355
356 356 """
357 357
358 358 def __init__(self, display_id=None):
359 359 if display_id is None:
360 360 display_id = _new_id()
361 361 self.display_id = display_id
362 362
363 363 def __repr__(self):
364 364 return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id)
365 365
366 366 def display(self, obj, **kwargs):
367 367 """Make a new display with my id, updating existing instances.
368 368
369 369 Parameters
370 370 ----------
371 371
372 372 obj:
373 373 object to display
374 374 **kwargs:
375 375 additional keyword arguments passed to display
376 376 """
377 377 display(obj, display_id=self.display_id, **kwargs)
378 378
379 379 def update(self, obj, **kwargs):
380 380 """Update existing displays with my id
381 381
382 382 Parameters
383 383 ----------
384 384
385 385 obj:
386 386 object to display
387 387 **kwargs:
388 388 additional keyword arguments passed to update_display
389 389 """
390 390 update_display(obj, display_id=self.display_id, **kwargs)
391 391
392 392
393 393 def display_pretty(*objs, **kwargs):
394 394 """Display the pretty (default) representation of an object.
395 395
396 396 Parameters
397 397 ----------
398 398 objs : tuple of objects
399 399 The Python objects to display, or if raw=True raw text data to
400 400 display.
401 401 raw : bool
402 402 Are the data objects raw data or Python objects that need to be
403 403 formatted before display? [default: False]
404 404 metadata : dict (optional)
405 405 Metadata to be associated with the specific mimetype output.
406 406 """
407 407 _display_mimetype('text/plain', objs, **kwargs)
408 408
409 409
410 410 def display_html(*objs, **kwargs):
411 411 """Display the HTML representation of an object.
412 412
413 413 Note: If raw=False and the object does not have a HTML
414 414 representation, no HTML will be shown.
415 415
416 416 Parameters
417 417 ----------
418 418 objs : tuple of objects
419 419 The Python objects to display, or if raw=True raw HTML data to
420 420 display.
421 421 raw : bool
422 422 Are the data objects raw data or Python objects that need to be
423 423 formatted before display? [default: False]
424 424 metadata : dict (optional)
425 425 Metadata to be associated with the specific mimetype output.
426 426 """
427 427 _display_mimetype('text/html', objs, **kwargs)
428 428
429 429
430 430 def display_markdown(*objs, **kwargs):
431 431 """Displays the Markdown representation of an object.
432 432
433 433 Parameters
434 434 ----------
435 435 objs : tuple of objects
436 436 The Python objects to display, or if raw=True raw markdown data to
437 437 display.
438 438 raw : bool
439 439 Are the data objects raw data or Python objects that need to be
440 440 formatted before display? [default: False]
441 441 metadata : dict (optional)
442 442 Metadata to be associated with the specific mimetype output.
443 443 """
444 444
445 445 _display_mimetype('text/markdown', objs, **kwargs)
446 446
447 447
448 448 def display_svg(*objs, **kwargs):
449 449 """Display the SVG representation of an object.
450 450
451 451 Parameters
452 452 ----------
453 453 objs : tuple of objects
454 454 The Python objects to display, or if raw=True raw svg data to
455 455 display.
456 456 raw : bool
457 457 Are the data objects raw data or Python objects that need to be
458 458 formatted before display? [default: False]
459 459 metadata : dict (optional)
460 460 Metadata to be associated with the specific mimetype output.
461 461 """
462 462 _display_mimetype('image/svg+xml', objs, **kwargs)
463 463
464 464
465 465 def display_png(*objs, **kwargs):
466 466 """Display the PNG representation of an object.
467 467
468 468 Parameters
469 469 ----------
470 470 objs : tuple of objects
471 471 The Python objects to display, or if raw=True raw png data to
472 472 display.
473 473 raw : bool
474 474 Are the data objects raw data or Python objects that need to be
475 475 formatted before display? [default: False]
476 476 metadata : dict (optional)
477 477 Metadata to be associated with the specific mimetype output.
478 478 """
479 479 _display_mimetype('image/png', objs, **kwargs)
480 480
481 481
482 482 def display_jpeg(*objs, **kwargs):
483 483 """Display the JPEG representation of an object.
484 484
485 485 Parameters
486 486 ----------
487 487 objs : tuple of objects
488 488 The Python objects to display, or if raw=True raw JPEG data to
489 489 display.
490 490 raw : bool
491 491 Are the data objects raw data or Python objects that need to be
492 492 formatted before display? [default: False]
493 493 metadata : dict (optional)
494 494 Metadata to be associated with the specific mimetype output.
495 495 """
496 496 _display_mimetype('image/jpeg', objs, **kwargs)
497 497
498 498
499 499 def display_latex(*objs, **kwargs):
500 500 """Display the LaTeX representation of an object.
501 501
502 502 Parameters
503 503 ----------
504 504 objs : tuple of objects
505 505 The Python objects to display, or if raw=True raw latex data to
506 506 display.
507 507 raw : bool
508 508 Are the data objects raw data or Python objects that need to be
509 509 formatted before display? [default: False]
510 510 metadata : dict (optional)
511 511 Metadata to be associated with the specific mimetype output.
512 512 """
513 513 _display_mimetype('text/latex', objs, **kwargs)
514 514
515 515
516 516 def display_json(*objs, **kwargs):
517 517 """Display the JSON representation of an object.
518 518
519 519 Note that not many frontends support displaying JSON.
520 520
521 521 Parameters
522 522 ----------
523 523 objs : tuple of objects
524 524 The Python objects to display, or if raw=True raw json data to
525 525 display.
526 526 raw : bool
527 527 Are the data objects raw data or Python objects that need to be
528 528 formatted before display? [default: False]
529 529 metadata : dict (optional)
530 530 Metadata to be associated with the specific mimetype output.
531 531 """
532 532 _display_mimetype('application/json', objs, **kwargs)
533 533
534 534
535 535 def display_javascript(*objs, **kwargs):
536 536 """Display the Javascript representation of an object.
537 537
538 538 Parameters
539 539 ----------
540 540 objs : tuple of objects
541 541 The Python objects to display, or if raw=True raw javascript data to
542 542 display.
543 543 raw : bool
544 544 Are the data objects raw data or Python objects that need to be
545 545 formatted before display? [default: False]
546 546 metadata : dict (optional)
547 547 Metadata to be associated with the specific mimetype output.
548 548 """
549 549 _display_mimetype('application/javascript', objs, **kwargs)
550 550
551 551
552 552 def display_pdf(*objs, **kwargs):
553 553 """Display the PDF representation of an object.
554 554
555 555 Parameters
556 556 ----------
557 557 objs : tuple of objects
558 558 The Python objects to display, or if raw=True raw javascript data to
559 559 display.
560 560 raw : bool
561 561 Are the data objects raw data or Python objects that need to be
562 562 formatted before display? [default: False]
563 563 metadata : dict (optional)
564 564 Metadata to be associated with the specific mimetype output.
565 565 """
566 566 _display_mimetype('application/pdf', objs, **kwargs)
567 567
568 568
569 569 #-----------------------------------------------------------------------------
570 570 # Smart classes
571 571 #-----------------------------------------------------------------------------
572 572
573 573
574 574 class DisplayObject(object):
575 575 """An object that wraps data to be displayed."""
576 576
577 577 _read_flags = 'r'
578 578 _show_mem_addr = False
579 579
580 580 def __init__(self, data=None, url=None, filename=None):
581 581 """Create a display object given raw data.
582 582
583 583 When this object is returned by an expression or passed to the
584 584 display function, it will result in the data being displayed
585 585 in the frontend. The MIME type of the data should match the
586 586 subclasses used, so the Png subclass should be used for 'image/png'
587 587 data. If the data is a URL, the data will first be downloaded
588 588 and then displayed. If
589 589
590 590 Parameters
591 591 ----------
592 592 data : unicode, str or bytes
593 593 The raw data or a URL or file to load the data from
594 594 url : unicode
595 595 A URL to download the data from.
596 596 filename : unicode
597 597 Path to a local file to load the data from.
598 598 """
599 599 if data is not None and isinstance(data, string_types):
600 600 if data.startswith('http') and url is None:
601 601 url = data
602 602 filename = None
603 603 data = None
604 604 elif _safe_exists(data) and filename is None:
605 605 url = None
606 606 filename = data
607 607 data = None
608 608
609 609 self.data = data
610 610 self.url = url
611 611 self.filename = None if filename is None else unicode_type(filename)
612 612
613 613 self.reload()
614 614 self._check_data()
615 615
616 616 def __repr__(self):
617 617 if not self._show_mem_addr:
618 618 cls = self.__class__
619 619 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
620 620 else:
621 621 r = super(DisplayObject, self).__repr__()
622 622 return r
623 623
624 624 def _check_data(self):
625 625 """Override in subclasses if there's something to check."""
626 626 pass
627 627
628 628 def reload(self):
629 629 """Reload the raw data from file or URL."""
630 630 if self.filename is not None:
631 631 with open(self.filename, self._read_flags) as f:
632 632 self.data = f.read()
633 633 elif self.url is not None:
634 634 try:
635 635 try:
636 636 from urllib.request import urlopen # Py3
637 637 except ImportError:
638 638 from urllib2 import urlopen
639 639 response = urlopen(self.url)
640 640 self.data = response.read()
641 641 # extract encoding from header, if there is one:
642 642 encoding = None
643 643 for sub in response.headers['content-type'].split(';'):
644 644 sub = sub.strip()
645 645 if sub.startswith('charset'):
646 646 encoding = sub.split('=')[-1].strip()
647 647 break
648 648 # decode data, if an encoding was specified
649 649 if encoding:
650 650 self.data = self.data.decode(encoding, 'replace')
651 651 except:
652 652 self.data = None
653 653
654 654 class TextDisplayObject(DisplayObject):
655 655 """Validate that display data is text"""
656 656 def _check_data(self):
657 657 if self.data is not None and not isinstance(self.data, string_types):
658 658 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
659 659
660 660 class Pretty(TextDisplayObject):
661 661
662 def _repr_pretty_(self):
663 return self.data
662 def _repr_pretty_(self, pp, cycle):
663 return pp.text(self.data)
664 664
665 665
666 666 class HTML(TextDisplayObject):
667 667
668 668 def _repr_html_(self):
669 669 return self.data
670 670
671 671 def __html__(self):
672 672 """
673 673 This method exists to inform other HTML-using modules (e.g. Markupsafe,
674 674 htmltag, etc) that this object is HTML and does not need things like
675 675 special characters (<>&) escaped.
676 676 """
677 677 return self._repr_html_()
678 678
679 679
680 680 class Markdown(TextDisplayObject):
681 681
682 682 def _repr_markdown_(self):
683 683 return self.data
684 684
685 685
686 686 class Math(TextDisplayObject):
687 687
688 688 def _repr_latex_(self):
689 689 s = self.data.strip('$')
690 690 return "$$%s$$" % s
691 691
692 692
693 693 class Latex(TextDisplayObject):
694 694
695 695 def _repr_latex_(self):
696 696 return self.data
697 697
698 698
699 699 class SVG(DisplayObject):
700 700
701 701 _read_flags = 'rb'
702 702 # wrap data in a property, which extracts the <svg> tag, discarding
703 703 # document headers
704 704 _data = None
705 705
706 706 @property
707 707 def data(self):
708 708 return self._data
709 709
710 710 @data.setter
711 711 def data(self, svg):
712 712 if svg is None:
713 713 self._data = None
714 714 return
715 715 # parse into dom object
716 716 from xml.dom import minidom
717 717 svg = cast_bytes_py2(svg)
718 718 x = minidom.parseString(svg)
719 719 # get svg tag (should be 1)
720 720 found_svg = x.getElementsByTagName('svg')
721 721 if found_svg:
722 722 svg = found_svg[0].toxml()
723 723 else:
724 724 # fallback on the input, trust the user
725 725 # but this is probably an error.
726 726 pass
727 727 svg = cast_unicode(svg)
728 728 self._data = svg
729 729
730 730 def _repr_svg_(self):
731 731 return self.data
732 732
733 733
734 734 class JSON(DisplayObject):
735 735 """JSON expects a JSON-able dict or list
736 736
737 737 not an already-serialized JSON string.
738 738
739 739 Scalar types (None, number, string) are not allowed, only dict or list containers.
740 740 """
741 741 # wrap data in a property, which warns about passing already-serialized JSON
742 742 _data = None
743 743 def _check_data(self):
744 744 if self.data is not None and not isinstance(self.data, (dict, list)):
745 745 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
746 746
747 747 @property
748 748 def data(self):
749 749 return self._data
750 750
751 751 @data.setter
752 752 def data(self, data):
753 753 if isinstance(data, string_types):
754 754 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
755 755 data = json.loads(data)
756 756 self._data = data
757 757
758 758 def _repr_json_(self):
759 759 return self.data
760 760
761 761 css_t = """$("head").append($("<link/>").attr({
762 762 rel: "stylesheet",
763 763 type: "text/css",
764 764 href: "%s"
765 765 }));
766 766 """
767 767
768 768 lib_t1 = """$.getScript("%s", function () {
769 769 """
770 770 lib_t2 = """});
771 771 """
772 772
773 773 class Javascript(TextDisplayObject):
774 774
775 775 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
776 776 """Create a Javascript display object given raw data.
777 777
778 778 When this object is returned by an expression or passed to the
779 779 display function, it will result in the data being displayed
780 780 in the frontend. If the data is a URL, the data will first be
781 781 downloaded and then displayed.
782 782
783 783 In the Notebook, the containing element will be available as `element`,
784 784 and jQuery will be available. Content appended to `element` will be
785 785 visible in the output area.
786 786
787 787 Parameters
788 788 ----------
789 789 data : unicode, str or bytes
790 790 The Javascript source code or a URL to download it from.
791 791 url : unicode
792 792 A URL to download the data from.
793 793 filename : unicode
794 794 Path to a local file to load the data from.
795 795 lib : list or str
796 796 A sequence of Javascript library URLs to load asynchronously before
797 797 running the source code. The full URLs of the libraries should
798 798 be given. A single Javascript library URL can also be given as a
799 799 string.
800 800 css: : list or str
801 801 A sequence of css files to load before running the source code.
802 802 The full URLs of the css files should be given. A single css URL
803 803 can also be given as a string.
804 804 """
805 805 if isinstance(lib, string_types):
806 806 lib = [lib]
807 807 elif lib is None:
808 808 lib = []
809 809 if isinstance(css, string_types):
810 810 css = [css]
811 811 elif css is None:
812 812 css = []
813 813 if not isinstance(lib, (list,tuple)):
814 814 raise TypeError('expected sequence, got: %r' % lib)
815 815 if not isinstance(css, (list,tuple)):
816 816 raise TypeError('expected sequence, got: %r' % css)
817 817 self.lib = lib
818 818 self.css = css
819 819 super(Javascript, self).__init__(data=data, url=url, filename=filename)
820 820
821 821 def _repr_javascript_(self):
822 822 r = ''
823 823 for c in self.css:
824 824 r += css_t % c
825 825 for l in self.lib:
826 826 r += lib_t1 % l
827 827 r += self.data
828 828 r += lib_t2*len(self.lib)
829 829 return r
830 830
831 831 # constants for identifying png/jpeg data
832 832 _PNG = b'\x89PNG\r\n\x1a\n'
833 833 _JPEG = b'\xff\xd8'
834 834
835 835 def _pngxy(data):
836 836 """read the (width, height) from a PNG header"""
837 837 ihdr = data.index(b'IHDR')
838 838 # next 8 bytes are width/height
839 839 w4h4 = data[ihdr+4:ihdr+12]
840 840 return struct.unpack('>ii', w4h4)
841 841
842 842 def _jpegxy(data):
843 843 """read the (width, height) from a JPEG header"""
844 844 # adapted from http://www.64lines.com/jpeg-width-height
845 845
846 846 idx = 4
847 847 while True:
848 848 block_size = struct.unpack('>H', data[idx:idx+2])[0]
849 849 idx = idx + block_size
850 850 if data[idx:idx+2] == b'\xFF\xC0':
851 851 # found Start of Frame
852 852 iSOF = idx
853 853 break
854 854 else:
855 855 # read another block
856 856 idx += 2
857 857
858 858 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
859 859 return w, h
860 860
861 861 class Image(DisplayObject):
862 862
863 863 _read_flags = 'rb'
864 864 _FMT_JPEG = u'jpeg'
865 865 _FMT_PNG = u'png'
866 866 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
867 867
868 868 def __init__(self, data=None, url=None, filename=None, format=None,
869 869 embed=None, width=None, height=None, retina=False,
870 870 unconfined=False, metadata=None):
871 871 """Create a PNG/JPEG image object given raw data.
872 872
873 873 When this object is returned by an input cell or passed to the
874 874 display function, it will result in the image being displayed
875 875 in the frontend.
876 876
877 877 Parameters
878 878 ----------
879 879 data : unicode, str or bytes
880 880 The raw image data or a URL or filename to load the data from.
881 881 This always results in embedded image data.
882 882 url : unicode
883 883 A URL to download the data from. If you specify `url=`,
884 884 the image data will not be embedded unless you also specify `embed=True`.
885 885 filename : unicode
886 886 Path to a local file to load the data from.
887 887 Images from a file are always embedded.
888 888 format : unicode
889 889 The format of the image data (png/jpeg/jpg). If a filename or URL is given
890 890 for format will be inferred from the filename extension.
891 891 embed : bool
892 892 Should the image data be embedded using a data URI (True) or be
893 893 loaded using an <img> tag. Set this to True if you want the image
894 894 to be viewable later with no internet connection in the notebook.
895 895
896 896 Default is `True`, unless the keyword argument `url` is set, then
897 897 default value is `False`.
898 898
899 899 Note that QtConsole is not able to display images if `embed` is set to `False`
900 900 width : int
901 901 Width in pixels to which to constrain the image in html
902 902 height : int
903 903 Height in pixels to which to constrain the image in html
904 904 retina : bool
905 905 Automatically set the width and height to half of the measured
906 906 width and height.
907 907 This only works for embedded images because it reads the width/height
908 908 from image data.
909 909 For non-embedded images, you can just set the desired display width
910 910 and height directly.
911 911 unconfined: bool
912 912 Set unconfined=True to disable max-width confinement of the image.
913 913 metadata: dict
914 914 Specify extra metadata to attach to the image.
915 915
916 916 Examples
917 917 --------
918 918 # embedded image data, works in qtconsole and notebook
919 919 # when passed positionally, the first arg can be any of raw image data,
920 920 # a URL, or a filename from which to load image data.
921 921 # The result is always embedding image data for inline images.
922 922 Image('http://www.google.fr/images/srpr/logo3w.png')
923 923 Image('/path/to/image.jpg')
924 924 Image(b'RAW_PNG_DATA...')
925 925
926 926 # Specifying Image(url=...) does not embed the image data,
927 927 # it only generates `<img>` tag with a link to the source.
928 928 # This will not work in the qtconsole or offline.
929 929 Image(url='http://www.google.fr/images/srpr/logo3w.png')
930 930
931 931 """
932 932 if filename is not None:
933 933 ext = self._find_ext(filename)
934 934 elif url is not None:
935 935 ext = self._find_ext(url)
936 936 elif data is None:
937 937 raise ValueError("No image data found. Expecting filename, url, or data.")
938 938 elif isinstance(data, string_types) and (
939 939 data.startswith('http') or _safe_exists(data)
940 940 ):
941 941 ext = self._find_ext(data)
942 942 else:
943 943 ext = None
944 944
945 945 if format is None:
946 946 if ext is not None:
947 947 if ext == u'jpg' or ext == u'jpeg':
948 948 format = self._FMT_JPEG
949 949 if ext == u'png':
950 950 format = self._FMT_PNG
951 951 else:
952 952 format = ext.lower()
953 953 elif isinstance(data, bytes):
954 954 # infer image type from image data header,
955 955 # only if format has not been specified.
956 956 if data[:2] == _JPEG:
957 957 format = self._FMT_JPEG
958 958
959 959 # failed to detect format, default png
960 960 if format is None:
961 961 format = 'png'
962 962
963 963 if format.lower() == 'jpg':
964 964 # jpg->jpeg
965 965 format = self._FMT_JPEG
966 966
967 967 self.format = unicode_type(format).lower()
968 968 self.embed = embed if embed is not None else (url is None)
969 969
970 970 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
971 971 raise ValueError("Cannot embed the '%s' image format" % (self.format))
972 972 self.width = width
973 973 self.height = height
974 974 self.retina = retina
975 975 self.unconfined = unconfined
976 976 self.metadata = metadata
977 977 super(Image, self).__init__(data=data, url=url, filename=filename)
978 978
979 979 if retina:
980 980 self._retina_shape()
981 981
982 982 def _retina_shape(self):
983 983 """load pixel-doubled width and height from image data"""
984 984 if not self.embed:
985 985 return
986 986 if self.format == 'png':
987 987 w, h = _pngxy(self.data)
988 988 elif self.format == 'jpeg':
989 989 w, h = _jpegxy(self.data)
990 990 else:
991 991 # retina only supports png
992 992 return
993 993 self.width = w // 2
994 994 self.height = h // 2
995 995
996 996 def reload(self):
997 997 """Reload the raw data from file or URL."""
998 998 if self.embed:
999 999 super(Image,self).reload()
1000 1000 if self.retina:
1001 1001 self._retina_shape()
1002 1002
1003 1003 def _repr_html_(self):
1004 1004 if not self.embed:
1005 1005 width = height = klass = ''
1006 1006 if self.width:
1007 1007 width = ' width="%d"' % self.width
1008 1008 if self.height:
1009 1009 height = ' height="%d"' % self.height
1010 1010 if self.unconfined:
1011 1011 klass = ' class="unconfined"'
1012 1012 return u'<img src="{url}"{width}{height}{klass}/>'.format(
1013 1013 url=self.url,
1014 1014 width=width,
1015 1015 height=height,
1016 1016 klass=klass,
1017 1017 )
1018 1018
1019 1019 def _data_and_metadata(self):
1020 1020 """shortcut for returning metadata with shape information, if defined"""
1021 1021 md = {}
1022 1022 if self.width:
1023 1023 md['width'] = self.width
1024 1024 if self.height:
1025 1025 md['height'] = self.height
1026 1026 if self.unconfined:
1027 1027 md['unconfined'] = self.unconfined
1028 1028 if self.metadata:
1029 1029 md.update(self.metadata)
1030 1030 if md:
1031 1031 return self.data, md
1032 1032 else:
1033 1033 return self.data
1034 1034
1035 1035 def _repr_png_(self):
1036 1036 if self.embed and self.format == u'png':
1037 1037 return self._data_and_metadata()
1038 1038
1039 1039 def _repr_jpeg_(self):
1040 1040 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
1041 1041 return self._data_and_metadata()
1042 1042
1043 1043 def _find_ext(self, s):
1044 1044 return unicode_type(s.split('.')[-1].lower())
1045 1045
1046 1046 class Video(DisplayObject):
1047 1047
1048 1048 def __init__(self, data=None, url=None, filename=None, embed=False, mimetype=None):
1049 1049 """Create a video object given raw data or an URL.
1050 1050
1051 1051 When this object is returned by an input cell or passed to the
1052 1052 display function, it will result in the video being displayed
1053 1053 in the frontend.
1054 1054
1055 1055 Parameters
1056 1056 ----------
1057 1057 data : unicode, str or bytes
1058 1058 The raw video data or a URL or filename to load the data from.
1059 1059 Raw data will require passing `embed=True`.
1060 1060 url : unicode
1061 1061 A URL for the video. If you specify `url=`,
1062 1062 the image data will not be embedded.
1063 1063 filename : unicode
1064 1064 Path to a local file containing the video.
1065 1065 Will be interpreted as a local URL unless `embed=True`.
1066 1066 embed : bool
1067 1067 Should the video be embedded using a data URI (True) or be
1068 1068 loaded using a <video> tag (False).
1069 1069
1070 1070 Since videos are large, embedding them should be avoided, if possible.
1071 1071 You must confirm embedding as your intention by passing `embed=True`.
1072 1072
1073 1073 Local files can be displayed with URLs without embedding the content, via::
1074 1074
1075 1075 Video('./video.mp4')
1076 1076
1077 1077 mimetype: unicode
1078 1078 Specify the mimetype for embedded videos.
1079 1079 Default will be guessed from file extension, if available.
1080 1080
1081 1081 Examples
1082 1082 --------
1083 1083
1084 1084 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1085 1085 Video('path/to/video.mp4')
1086 1086 Video('path/to/video.mp4', embed=True)
1087 1087 Video(b'raw-videodata', embed=True)
1088 1088 """
1089 1089 if url is None and isinstance(data, string_types) and data.startswith(('http:', 'https:')):
1090 1090 url = data
1091 1091 data = None
1092 1092 elif os.path.exists(data):
1093 1093 filename = data
1094 1094 data = None
1095 1095
1096 1096 if data and not embed:
1097 1097 msg = ''.join([
1098 1098 "To embed videos, you must pass embed=True ",
1099 1099 "(this may make your notebook files huge)\n",
1100 1100 "Consider passing Video(url='...')",
1101 1101 ])
1102 1102 raise ValueError(msg)
1103 1103
1104 1104 self.mimetype = mimetype
1105 1105 self.embed = embed
1106 1106 super(Video, self).__init__(data=data, url=url, filename=filename)
1107 1107
1108 1108 def _repr_html_(self):
1109 1109 # External URLs and potentially local files are not embedded into the
1110 1110 # notebook output.
1111 1111 if not self.embed:
1112 1112 url = self.url if self.url is not None else self.filename
1113 1113 output = """<video src="{0}" controls>
1114 1114 Your browser does not support the <code>video</code> element.
1115 1115 </video>""".format(url)
1116 1116 return output
1117 1117
1118 1118 # Embedded videos are base64-encoded.
1119 1119 mimetype = self.mimetype
1120 1120 if self.filename is not None:
1121 1121 if not mimetype:
1122 1122 mimetype, _ = mimetypes.guess_type(self.filename)
1123 1123
1124 1124 with open(self.filename, 'rb') as f:
1125 1125 video = f.read()
1126 1126 else:
1127 1127 video = self.data
1128 1128 if isinstance(video, unicode_type):
1129 1129 # unicode input is already b64-encoded
1130 1130 b64_video = video
1131 1131 else:
1132 1132 b64_video = base64_encode(video).decode('ascii').rstrip()
1133 1133
1134 1134 output = """<video controls>
1135 1135 <source src="data:{0};base64,{1}" type="{0}">
1136 1136 Your browser does not support the video tag.
1137 1137 </video>""".format(mimetype, b64_video)
1138 1138 return output
1139 1139
1140 1140 def reload(self):
1141 1141 # TODO
1142 1142 pass
1143 1143
1144 1144 def _repr_png_(self):
1145 1145 # TODO
1146 1146 pass
1147 1147 def _repr_jpeg_(self):
1148 1148 # TODO
1149 1149 pass
1150 1150
1151 1151 def clear_output(wait=False):
1152 1152 """Clear the output of the current cell receiving output.
1153 1153
1154 1154 Parameters
1155 1155 ----------
1156 1156 wait : bool [default: false]
1157 1157 Wait to clear the output until new output is available to replace it."""
1158 1158 from IPython.core.interactiveshell import InteractiveShell
1159 1159 if InteractiveShell.initialized():
1160 1160 InteractiveShell.instance().display_pub.clear_output(wait)
1161 1161 else:
1162 1162 print('\033[2K\r', end='')
1163 1163 sys.stdout.flush()
1164 1164 print('\033[2K\r', end='')
1165 1165 sys.stderr.flush()
1166 1166
1167 1167
1168 1168 @skip_doctest
1169 1169 def set_matplotlib_formats(*formats, **kwargs):
1170 1170 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
1171 1171
1172 1172 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1173 1173
1174 1174 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1175 1175
1176 1176 To set this in your config files use the following::
1177 1177
1178 1178 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1179 1179 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1180 1180
1181 1181 Parameters
1182 1182 ----------
1183 1183 *formats : strs
1184 1184 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1185 1185 **kwargs :
1186 1186 Keyword args will be relayed to ``figure.canvas.print_figure``.
1187 1187 """
1188 1188 from IPython.core.interactiveshell import InteractiveShell
1189 1189 from IPython.core.pylabtools import select_figure_formats
1190 1190 # build kwargs, starting with InlineBackend config
1191 1191 kw = {}
1192 1192 from ipykernel.pylab.config import InlineBackend
1193 1193 cfg = InlineBackend.instance()
1194 1194 kw.update(cfg.print_figure_kwargs)
1195 1195 kw.update(**kwargs)
1196 1196 shell = InteractiveShell.instance()
1197 1197 select_figure_formats(shell, formats, **kw)
1198 1198
1199 1199 @skip_doctest
1200 1200 def set_matplotlib_close(close=True):
1201 1201 """Set whether the inline backend closes all figures automatically or not.
1202 1202
1203 1203 By default, the inline backend used in the IPython Notebook will close all
1204 1204 matplotlib figures automatically after each cell is run. This means that
1205 1205 plots in different cells won't interfere. Sometimes, you may want to make
1206 1206 a plot in one cell and then refine it in later cells. This can be accomplished
1207 1207 by::
1208 1208
1209 1209 In [1]: set_matplotlib_close(False)
1210 1210
1211 1211 To set this in your config files use the following::
1212 1212
1213 1213 c.InlineBackend.close_figures = False
1214 1214
1215 1215 Parameters
1216 1216 ----------
1217 1217 close : bool
1218 1218 Should all matplotlib figures be automatically closed after each cell is
1219 1219 run?
1220 1220 """
1221 1221 from ipykernel.pylab.config import InlineBackend
1222 1222 cfg = InlineBackend.instance()
1223 1223 cfg.close_figures = close
1224 1224
@@ -1,331 +1,338
1 1 # Copyright (c) IPython Development Team.
2 2 # Distributed under the terms of the Modified BSD License.
3 3
4 4 import json
5 5 import os
6 6 import sys
7 7 import warnings
8 8
9 9 import nose.tools as nt
10 10
11 11 from IPython.core import display
12 12 from IPython.core.getipython import get_ipython
13 13 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
14 14 from IPython import paths as ipath
15 15 from IPython.testing.tools import AssertPrints, AssertNotPrints
16 16
17 17 import IPython.testing.decorators as dec
18 18
19 19 if sys.version_info < (3,):
20 20 import mock
21 21 else:
22 22 from unittest import mock
23 23
24 24 def test_image_size():
25 25 """Simple test for display.Image(args, width=x,height=y)"""
26 26 thisurl = 'http://www.google.fr/images/srpr/logo3w.png'
27 27 img = display.Image(url=thisurl, width=200, height=200)
28 28 nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
29 29 img = display.Image(url=thisurl, width=200)
30 30 nt.assert_equal(u'<img src="%s" width="200"/>' % (thisurl), img._repr_html_())
31 31 img = display.Image(url=thisurl)
32 32 nt.assert_equal(u'<img src="%s"/>' % (thisurl), img._repr_html_())
33 33 img = display.Image(url=thisurl, unconfined=True)
34 34 nt.assert_equal(u'<img src="%s" class="unconfined"/>' % (thisurl), img._repr_html_())
35 35
36 36 def test_retina_png():
37 37 here = os.path.dirname(__file__)
38 38 img = display.Image(os.path.join(here, "2x2.png"), retina=True)
39 39 nt.assert_equal(img.height, 1)
40 40 nt.assert_equal(img.width, 1)
41 41 data, md = img._repr_png_()
42 42 nt.assert_equal(md['width'], 1)
43 43 nt.assert_equal(md['height'], 1)
44 44
45 45 def test_retina_jpeg():
46 46 here = os.path.dirname(__file__)
47 47 img = display.Image(os.path.join(here, "2x2.jpg"), retina=True)
48 48 nt.assert_equal(img.height, 1)
49 49 nt.assert_equal(img.width, 1)
50 50 data, md = img._repr_jpeg_()
51 51 nt.assert_equal(md['width'], 1)
52 52 nt.assert_equal(md['height'], 1)
53 53
54 54 def test_base64image():
55 55 display.Image("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AAAAACAAHiIbwzAAAAAElFTkSuQmCC")
56 56
57 57 def test_image_filename_defaults():
58 58 '''test format constraint, and validity of jpeg and png'''
59 59 tpath = ipath.get_ipython_package_dir()
60 60 nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat.gif'),
61 61 embed=True)
62 62 nt.assert_raises(ValueError, display.Image)
63 63 nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True)
64 64 # check boths paths to allow packages to test at build and install time
65 65 imgfile = os.path.join(tpath, 'core/tests/2x2.png')
66 66 img = display.Image(filename=imgfile)
67 67 nt.assert_equal('png', img.format)
68 68 nt.assert_is_not_none(img._repr_png_())
69 69 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
70 70 nt.assert_equal('jpeg', img.format)
71 71 nt.assert_is_none(img._repr_jpeg_())
72 72
73 73 def _get_inline_config():
74 74 from ipykernel.pylab.config import InlineBackend
75 75 return InlineBackend.instance()
76 76
77 77 @dec.skip_without('matplotlib')
78 78 def test_set_matplotlib_close():
79 79 cfg = _get_inline_config()
80 80 cfg.close_figures = False
81 81 display.set_matplotlib_close()
82 82 assert cfg.close_figures
83 83 display.set_matplotlib_close(False)
84 84 assert not cfg.close_figures
85 85
86 86 _fmt_mime_map = {
87 87 'png': 'image/png',
88 88 'jpeg': 'image/jpeg',
89 89 'pdf': 'application/pdf',
90 90 'retina': 'image/png',
91 91 'svg': 'image/svg+xml',
92 92 }
93 93
94 94 @dec.skip_without('matplotlib')
95 95 def test_set_matplotlib_formats():
96 96 from matplotlib.figure import Figure
97 97 formatters = get_ipython().display_formatter.formatters
98 98 for formats in [
99 99 ('png',),
100 100 ('pdf', 'svg'),
101 101 ('jpeg', 'retina', 'png'),
102 102 (),
103 103 ]:
104 104 active_mimes = {_fmt_mime_map[fmt] for fmt in formats}
105 105 display.set_matplotlib_formats(*formats)
106 106 for mime, f in formatters.items():
107 107 if mime in active_mimes:
108 108 nt.assert_in(Figure, f)
109 109 else:
110 110 nt.assert_not_in(Figure, f)
111 111
112 112 @dec.skip_without('matplotlib')
113 113 def test_set_matplotlib_formats_kwargs():
114 114 from matplotlib.figure import Figure
115 115 ip = get_ipython()
116 116 cfg = _get_inline_config()
117 117 cfg.print_figure_kwargs.update(dict(foo='bar'))
118 118 kwargs = dict(quality=10)
119 119 display.set_matplotlib_formats('png', **kwargs)
120 120 formatter = ip.display_formatter.formatters['image/png']
121 121 f = formatter.lookup_by_type(Figure)
122 122 cell = f.__closure__[0].cell_contents
123 123 expected = kwargs
124 124 expected.update(cfg.print_figure_kwargs)
125 125 nt.assert_equal(cell, expected)
126 126
127 127 def test_display_available():
128 128 """
129 129 Test that display is available without import
130 130
131 131 We don't really care if it's in builtin or anything else, but it should
132 132 always be available.
133 133 """
134 134 ip = get_ipython()
135 135 with AssertNotPrints('NameError'):
136 136 ip.run_cell('display')
137 137 try:
138 138 ip.run_cell('del display')
139 139 except NameError:
140 140 pass # it's ok, it might be in builtins
141 141 # even if deleted it should be back
142 142 with AssertNotPrints('NameError'):
143 143 ip.run_cell('display')
144 144
145 def test_textdisplayobj_pretty_repr():
146 p = display.Pretty("This is a simple test")
147 nt.assert_equal(repr(p), '<IPython.core.display.Pretty object>')
148 nt.assert_equal(p.data, 'This is a simple test')
149
150 p._show_mem_addr = True
151 nt.assert_equal(repr(p), object.__repr__(p))
145 152
146 153 def test_displayobject_repr():
147 154 h = display.HTML('<br />')
148 155 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
149 156 h._show_mem_addr = True
150 157 nt.assert_equal(repr(h), object.__repr__(h))
151 158 h._show_mem_addr = False
152 159 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
153 160
154 161 j = display.Javascript('')
155 162 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
156 163 j._show_mem_addr = True
157 164 nt.assert_equal(repr(j), object.__repr__(j))
158 165 j._show_mem_addr = False
159 166 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
160 167
161 168 def test_json():
162 169 d = {'a': 5}
163 170 lis = [d]
164 171 j = display.JSON(d)
165 172 nt.assert_equal(j._repr_json_(), d)
166 173
167 174 with warnings.catch_warnings(record=True) as w:
168 175 warnings.simplefilter("always")
169 176 j = display.JSON(json.dumps(d))
170 177 nt.assert_equal(len(w), 1)
171 178 nt.assert_equal(j._repr_json_(), d)
172 179
173 180 j = display.JSON(lis)
174 181 nt.assert_equal(j._repr_json_(), lis)
175 182
176 183 with warnings.catch_warnings(record=True) as w:
177 184 warnings.simplefilter("always")
178 185 j = display.JSON(json.dumps(lis))
179 186 nt.assert_equal(len(w), 1)
180 187 nt.assert_equal(j._repr_json_(), lis)
181 188
182 189 def test_video_embedding():
183 190 """use a tempfile, with dummy-data, to ensure that video embedding doesn't crash"""
184 191 v = display.Video("http://ignored")
185 192 assert not v.embed
186 193 html = v._repr_html_()
187 194 nt.assert_not_in('src="data:', html)
188 195 nt.assert_in('src="http://ignored"', html)
189 196
190 197 with nt.assert_raises(ValueError):
191 198 v = display.Video(b'abc')
192 199
193 200 with NamedFileInTemporaryDirectory('test.mp4') as f:
194 201 f.write(b'abc')
195 202 f.close()
196 203
197 204 v = display.Video(f.name)
198 205 assert not v.embed
199 206 html = v._repr_html_()
200 207 nt.assert_not_in('src="data:', html)
201 208
202 209 v = display.Video(f.name, embed=True)
203 210 html = v._repr_html_()
204 211 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
205 212
206 213 v = display.Video(f.name, embed=True, mimetype='video/other')
207 214 html = v._repr_html_()
208 215 nt.assert_in('src="data:video/other;base64,YWJj"',html)
209 216
210 217 v = display.Video(b'abc', embed=True, mimetype='video/mp4')
211 218 html = v._repr_html_()
212 219 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
213 220
214 221 v = display.Video(u'YWJj', embed=True, mimetype='video/xyz')
215 222 html = v._repr_html_()
216 223 nt.assert_in('src="data:video/xyz;base64,YWJj"',html)
217 224
218 225
219 226 def test_display_id():
220 227 ip = get_ipython()
221 228 with mock.patch.object(ip.display_pub, 'publish') as pub:
222 229 handle = display.display('x')
223 230 nt.assert_is(handle, None)
224 231 handle = display.display('y', display_id='secret')
225 232 nt.assert_is_instance(handle, display.DisplayHandle)
226 233 handle2 = display.display('z', display_id=True)
227 234 nt.assert_is_instance(handle2, display.DisplayHandle)
228 235 nt.assert_not_equal(handle.display_id, handle2.display_id)
229 236
230 237 nt.assert_equal(pub.call_count, 3)
231 238 args, kwargs = pub.call_args_list[0]
232 239 nt.assert_equal(args, ())
233 240 nt.assert_equal(kwargs, {
234 241 'data': {
235 242 'text/plain': repr('x')
236 243 },
237 244 'metadata': {},
238 245 })
239 246 args, kwargs = pub.call_args_list[1]
240 247 nt.assert_equal(args, ())
241 248 nt.assert_equal(kwargs, {
242 249 'data': {
243 250 'text/plain': repr('y')
244 251 },
245 252 'metadata': {},
246 253 'transient': {
247 254 'display_id': handle.display_id,
248 255 },
249 256 })
250 257 args, kwargs = pub.call_args_list[2]
251 258 nt.assert_equal(args, ())
252 259 nt.assert_equal(kwargs, {
253 260 'data': {
254 261 'text/plain': repr('z')
255 262 },
256 263 'metadata': {},
257 264 'transient': {
258 265 'display_id': handle2.display_id,
259 266 },
260 267 })
261 268
262 269
263 270 def test_update_display():
264 271 ip = get_ipython()
265 272 with mock.patch.object(ip.display_pub, 'publish') as pub:
266 273 with nt.assert_raises(TypeError):
267 274 display.update_display('x')
268 275 display.update_display('x', display_id='1')
269 276 display.update_display('y', display_id='2')
270 277 args, kwargs = pub.call_args_list[0]
271 278 nt.assert_equal(args, ())
272 279 nt.assert_equal(kwargs, {
273 280 'data': {
274 281 'text/plain': repr('x')
275 282 },
276 283 'metadata': {},
277 284 'transient': {
278 285 'display_id': '1',
279 286 },
280 287 'update': True,
281 288 })
282 289 args, kwargs = pub.call_args_list[1]
283 290 nt.assert_equal(args, ())
284 291 nt.assert_equal(kwargs, {
285 292 'data': {
286 293 'text/plain': repr('y')
287 294 },
288 295 'metadata': {},
289 296 'transient': {
290 297 'display_id': '2',
291 298 },
292 299 'update': True,
293 300 })
294 301
295 302
296 303 def test_display_handle():
297 304 ip = get_ipython()
298 305 handle = display.DisplayHandle()
299 306 if sys.version_info < (3,):
300 307 nt.assert_is_instance(handle.display_id, unicode)
301 308 else:
302 309 nt.assert_is_instance(handle.display_id, str)
303 310 handle = display.DisplayHandle('my-id')
304 311 nt.assert_equal(handle.display_id, 'my-id')
305 312 with mock.patch.object(ip.display_pub, 'publish') as pub:
306 313 handle.display('x')
307 314 handle.update('y')
308 315
309 316 args, kwargs = pub.call_args_list[0]
310 317 nt.assert_equal(args, ())
311 318 nt.assert_equal(kwargs, {
312 319 'data': {
313 320 'text/plain': repr('x')
314 321 },
315 322 'metadata': {},
316 323 'transient': {
317 324 'display_id': handle.display_id,
318 325 }
319 326 })
320 327 args, kwargs = pub.call_args_list[1]
321 328 nt.assert_equal(args, ())
322 329 nt.assert_equal(kwargs, {
323 330 'data': {
324 331 'text/plain': repr('y')
325 332 },
326 333 'metadata': {},
327 334 'transient': {
328 335 'display_id': handle.display_id,
329 336 },
330 337 'update': True,
331 338 })
General Comments 0
You need to be logged in to leave comments. Login now