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