##// END OF EJS Templates
handle case-insensitivity
Michael Penkov -
Show More
@@ -1,1433 +1,1445 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 __init__(self, data=None, url=None, filename=None, metadata=None):
670 if data and data.startswith("<iframe ") and data.endswith("</iframe>"):
670 def warn():
671 if not data:
672 return False
673
674 #
675 # Avoid calling lower() on the entire data, because it could be a
676 # long string and we're only interested in its beginning and end.
677 #
678 prefix = data[:10].lower()
679 suffix = data[-10:].lower()
680 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
681
682 if warn():
671 683 warnings.warn("Consider using IPython.display.IFrame instead")
672 684 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
673 685
674 686 def _repr_html_(self):
675 687 return self._data_and_metadata()
676 688
677 689 def __html__(self):
678 690 """
679 691 This method exists to inform other HTML-using modules (e.g. Markupsafe,
680 692 htmltag, etc) that this object is HTML and does not need things like
681 693 special characters (<>&) escaped.
682 694 """
683 695 return self._repr_html_()
684 696
685 697
686 698 class Markdown(TextDisplayObject):
687 699
688 700 def _repr_markdown_(self):
689 701 return self._data_and_metadata()
690 702
691 703
692 704 class Math(TextDisplayObject):
693 705
694 706 def _repr_latex_(self):
695 707 s = "$$%s$$" % self.data.strip('$')
696 708 if self.metadata:
697 709 return s, deepcopy(self.metadata)
698 710 else:
699 711 return s
700 712
701 713
702 714 class Latex(TextDisplayObject):
703 715
704 716 def _repr_latex_(self):
705 717 return self._data_and_metadata()
706 718
707 719
708 720 class SVG(DisplayObject):
709 721
710 722 _read_flags = 'rb'
711 723 # wrap data in a property, which extracts the <svg> tag, discarding
712 724 # document headers
713 725 _data = None
714 726
715 727 @property
716 728 def data(self):
717 729 return self._data
718 730
719 731 @data.setter
720 732 def data(self, svg):
721 733 if svg is None:
722 734 self._data = None
723 735 return
724 736 # parse into dom object
725 737 from xml.dom import minidom
726 738 x = minidom.parseString(svg)
727 739 # get svg tag (should be 1)
728 740 found_svg = x.getElementsByTagName('svg')
729 741 if found_svg:
730 742 svg = found_svg[0].toxml()
731 743 else:
732 744 # fallback on the input, trust the user
733 745 # but this is probably an error.
734 746 pass
735 747 svg = cast_unicode(svg)
736 748 self._data = svg
737 749
738 750 def _repr_svg_(self):
739 751 return self._data_and_metadata()
740 752
741 753 class ProgressBar(DisplayObject):
742 754 """Progressbar supports displaying a progressbar like element
743 755 """
744 756 def __init__(self, total):
745 757 """Creates a new progressbar
746 758
747 759 Parameters
748 760 ----------
749 761 total : int
750 762 maximum size of the progressbar
751 763 """
752 764 self.total = total
753 765 self._progress = 0
754 766 self.html_width = '60ex'
755 767 self.text_width = 60
756 768 self._display_id = hexlify(os.urandom(8)).decode('ascii')
757 769
758 770 def __repr__(self):
759 771 fraction = self.progress / self.total
760 772 filled = '=' * int(fraction * self.text_width)
761 773 rest = ' ' * (self.text_width - len(filled))
762 774 return '[{}{}] {}/{}'.format(
763 775 filled, rest,
764 776 self.progress, self.total,
765 777 )
766 778
767 779 def _repr_html_(self):
768 780 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
769 781 self.html_width, self.total, self.progress)
770 782
771 783 def display(self):
772 784 display(self, display_id=self._display_id)
773 785
774 786 def update(self):
775 787 display(self, display_id=self._display_id, update=True)
776 788
777 789 @property
778 790 def progress(self):
779 791 return self._progress
780 792
781 793 @progress.setter
782 794 def progress(self, value):
783 795 self._progress = value
784 796 self.update()
785 797
786 798 def __iter__(self):
787 799 self.display()
788 800 self._progress = -1 # First iteration is 0
789 801 return self
790 802
791 803 def __next__(self):
792 804 """Returns current value and increments display by one."""
793 805 self.progress += 1
794 806 if self.progress < self.total:
795 807 return self.progress
796 808 else:
797 809 raise StopIteration()
798 810
799 811 class JSON(DisplayObject):
800 812 """JSON expects a JSON-able dict or list
801 813
802 814 not an already-serialized JSON string.
803 815
804 816 Scalar types (None, number, string) are not allowed, only dict or list containers.
805 817 """
806 818 # wrap data in a property, which warns about passing already-serialized JSON
807 819 _data = None
808 820 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
809 821 """Create a JSON display object given raw data.
810 822
811 823 Parameters
812 824 ----------
813 825 data : dict or list
814 826 JSON data to display. Not an already-serialized JSON string.
815 827 Scalar types (None, number, string) are not allowed, only dict
816 828 or list containers.
817 829 url : unicode
818 830 A URL to download the data from.
819 831 filename : unicode
820 832 Path to a local file to load the data from.
821 833 expanded : boolean
822 834 Metadata to control whether a JSON display component is expanded.
823 835 metadata: dict
824 836 Specify extra metadata to attach to the json display object.
825 837 root : str
826 838 The name of the root element of the JSON tree
827 839 """
828 840 self.metadata = {
829 841 'expanded': expanded,
830 842 'root': root,
831 843 }
832 844 if metadata:
833 845 self.metadata.update(metadata)
834 846 if kwargs:
835 847 self.metadata.update(kwargs)
836 848 super(JSON, self).__init__(data=data, url=url, filename=filename)
837 849
838 850 def _check_data(self):
839 851 if self.data is not None and not isinstance(self.data, (dict, list)):
840 852 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
841 853
842 854 @property
843 855 def data(self):
844 856 return self._data
845 857
846 858 @data.setter
847 859 def data(self, data):
848 860 if isinstance(data, str):
849 861 if getattr(self, 'filename', None) is None:
850 862 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
851 863 data = json.loads(data)
852 864 self._data = data
853 865
854 866 def _data_and_metadata(self):
855 867 return self.data, self.metadata
856 868
857 869 def _repr_json_(self):
858 870 return self._data_and_metadata()
859 871
860 872 _css_t = """var link = document.createElement("link");
861 873 link.ref = "stylesheet";
862 874 link.type = "text/css";
863 875 link.href = "%s";
864 876 document.head.appendChild(link);
865 877 """
866 878
867 879 _lib_t1 = """new Promise(function(resolve, reject) {
868 880 var script = document.createElement("script");
869 881 script.onload = resolve;
870 882 script.onerror = reject;
871 883 script.src = "%s";
872 884 document.head.appendChild(script);
873 885 }).then(() => {
874 886 """
875 887
876 888 _lib_t2 = """
877 889 });"""
878 890
879 891 class GeoJSON(JSON):
880 892 """GeoJSON expects JSON-able dict
881 893
882 894 not an already-serialized JSON string.
883 895
884 896 Scalar types (None, number, string) are not allowed, only dict containers.
885 897 """
886 898
887 899 def __init__(self, *args, **kwargs):
888 900 """Create a GeoJSON display object given raw data.
889 901
890 902 Parameters
891 903 ----------
892 904 data : dict or list
893 905 VegaLite data. Not an already-serialized JSON string.
894 906 Scalar types (None, number, string) are not allowed, only dict
895 907 or list containers.
896 908 url_template : string
897 909 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
898 910 layer_options : dict
899 911 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
900 912 url : unicode
901 913 A URL to download the data from.
902 914 filename : unicode
903 915 Path to a local file to load the data from.
904 916 metadata: dict
905 917 Specify extra metadata to attach to the json display object.
906 918
907 919 Examples
908 920 --------
909 921
910 922 The following will display an interactive map of Mars with a point of
911 923 interest on frontend that do support GeoJSON display.
912 924
913 925 >>> from IPython.display import GeoJSON
914 926
915 927 >>> GeoJSON(data={
916 928 ... "type": "Feature",
917 929 ... "geometry": {
918 930 ... "type": "Point",
919 931 ... "coordinates": [-81.327, 296.038]
920 932 ... }
921 933 ... },
922 934 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
923 935 ... layer_options={
924 936 ... "basemap_id": "celestia_mars-shaded-16k_global",
925 937 ... "attribution" : "Celestia/praesepe",
926 938 ... "minZoom" : 0,
927 939 ... "maxZoom" : 18,
928 940 ... })
929 941 <IPython.core.display.GeoJSON object>
930 942
931 943 In the terminal IPython, you will only see the text representation of
932 944 the GeoJSON object.
933 945
934 946 """
935 947
936 948 super(GeoJSON, self).__init__(*args, **kwargs)
937 949
938 950
939 951 def _ipython_display_(self):
940 952 bundle = {
941 953 'application/geo+json': self.data,
942 954 'text/plain': '<IPython.display.GeoJSON object>'
943 955 }
944 956 metadata = {
945 957 'application/geo+json': self.metadata
946 958 }
947 959 display(bundle, metadata=metadata, raw=True)
948 960
949 961 class Javascript(TextDisplayObject):
950 962
951 963 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
952 964 """Create a Javascript display object given raw data.
953 965
954 966 When this object is returned by an expression or passed to the
955 967 display function, it will result in the data being displayed
956 968 in the frontend. If the data is a URL, the data will first be
957 969 downloaded and then displayed.
958 970
959 971 In the Notebook, the containing element will be available as `element`,
960 972 and jQuery will be available. Content appended to `element` will be
961 973 visible in the output area.
962 974
963 975 Parameters
964 976 ----------
965 977 data : unicode, str or bytes
966 978 The Javascript source code or a URL to download it from.
967 979 url : unicode
968 980 A URL to download the data from.
969 981 filename : unicode
970 982 Path to a local file to load the data from.
971 983 lib : list or str
972 984 A sequence of Javascript library URLs to load asynchronously before
973 985 running the source code. The full URLs of the libraries should
974 986 be given. A single Javascript library URL can also be given as a
975 987 string.
976 988 css: : list or str
977 989 A sequence of css files to load before running the source code.
978 990 The full URLs of the css files should be given. A single css URL
979 991 can also be given as a string.
980 992 """
981 993 if isinstance(lib, str):
982 994 lib = [lib]
983 995 elif lib is None:
984 996 lib = []
985 997 if isinstance(css, str):
986 998 css = [css]
987 999 elif css is None:
988 1000 css = []
989 1001 if not isinstance(lib, (list,tuple)):
990 1002 raise TypeError('expected sequence, got: %r' % lib)
991 1003 if not isinstance(css, (list,tuple)):
992 1004 raise TypeError('expected sequence, got: %r' % css)
993 1005 self.lib = lib
994 1006 self.css = css
995 1007 super(Javascript, self).__init__(data=data, url=url, filename=filename)
996 1008
997 1009 def _repr_javascript_(self):
998 1010 r = ''
999 1011 for c in self.css:
1000 1012 r += _css_t % c
1001 1013 for l in self.lib:
1002 1014 r += _lib_t1 % l
1003 1015 r += self.data
1004 1016 r += _lib_t2*len(self.lib)
1005 1017 return r
1006 1018
1007 1019 # constants for identifying png/jpeg data
1008 1020 _PNG = b'\x89PNG\r\n\x1a\n'
1009 1021 _JPEG = b'\xff\xd8'
1010 1022
1011 1023 def _pngxy(data):
1012 1024 """read the (width, height) from a PNG header"""
1013 1025 ihdr = data.index(b'IHDR')
1014 1026 # next 8 bytes are width/height
1015 1027 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
1016 1028
1017 1029 def _jpegxy(data):
1018 1030 """read the (width, height) from a JPEG header"""
1019 1031 # adapted from http://www.64lines.com/jpeg-width-height
1020 1032
1021 1033 idx = 4
1022 1034 while True:
1023 1035 block_size = struct.unpack('>H', data[idx:idx+2])[0]
1024 1036 idx = idx + block_size
1025 1037 if data[idx:idx+2] == b'\xFF\xC0':
1026 1038 # found Start of Frame
1027 1039 iSOF = idx
1028 1040 break
1029 1041 else:
1030 1042 # read another block
1031 1043 idx += 2
1032 1044
1033 1045 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
1034 1046 return w, h
1035 1047
1036 1048 def _gifxy(data):
1037 1049 """read the (width, height) from a GIF header"""
1038 1050 return struct.unpack('<HH', data[6:10])
1039 1051
1040 1052
1041 1053 class Image(DisplayObject):
1042 1054
1043 1055 _read_flags = 'rb'
1044 1056 _FMT_JPEG = u'jpeg'
1045 1057 _FMT_PNG = u'png'
1046 1058 _FMT_GIF = u'gif'
1047 1059 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
1048 1060 _MIMETYPES = {
1049 1061 _FMT_PNG: 'image/png',
1050 1062 _FMT_JPEG: 'image/jpeg',
1051 1063 _FMT_GIF: 'image/gif',
1052 1064 }
1053 1065
1054 1066 def __init__(self, data=None, url=None, filename=None, format=None,
1055 1067 embed=None, width=None, height=None, retina=False,
1056 1068 unconfined=False, metadata=None):
1057 1069 """Create a PNG/JPEG/GIF image object given raw data.
1058 1070
1059 1071 When this object is returned by an input cell or passed to the
1060 1072 display function, it will result in the image being displayed
1061 1073 in the frontend.
1062 1074
1063 1075 Parameters
1064 1076 ----------
1065 1077 data : unicode, str or bytes
1066 1078 The raw image data or a URL or filename to load the data from.
1067 1079 This always results in embedded image data.
1068 1080 url : unicode
1069 1081 A URL to download the data from. If you specify `url=`,
1070 1082 the image data will not be embedded unless you also specify `embed=True`.
1071 1083 filename : unicode
1072 1084 Path to a local file to load the data from.
1073 1085 Images from a file are always embedded.
1074 1086 format : unicode
1075 1087 The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
1076 1088 for format will be inferred from the filename extension.
1077 1089 embed : bool
1078 1090 Should the image data be embedded using a data URI (True) or be
1079 1091 loaded using an <img> tag. Set this to True if you want the image
1080 1092 to be viewable later with no internet connection in the notebook.
1081 1093
1082 1094 Default is `True`, unless the keyword argument `url` is set, then
1083 1095 default value is `False`.
1084 1096
1085 1097 Note that QtConsole is not able to display images if `embed` is set to `False`
1086 1098 width : int
1087 1099 Width in pixels to which to constrain the image in html
1088 1100 height : int
1089 1101 Height in pixels to which to constrain the image in html
1090 1102 retina : bool
1091 1103 Automatically set the width and height to half of the measured
1092 1104 width and height.
1093 1105 This only works for embedded images because it reads the width/height
1094 1106 from image data.
1095 1107 For non-embedded images, you can just set the desired display width
1096 1108 and height directly.
1097 1109 unconfined: bool
1098 1110 Set unconfined=True to disable max-width confinement of the image.
1099 1111 metadata: dict
1100 1112 Specify extra metadata to attach to the image.
1101 1113
1102 1114 Examples
1103 1115 --------
1104 1116 # embedded image data, works in qtconsole and notebook
1105 1117 # when passed positionally, the first arg can be any of raw image data,
1106 1118 # a URL, or a filename from which to load image data.
1107 1119 # The result is always embedding image data for inline images.
1108 1120 Image('http://www.google.fr/images/srpr/logo3w.png')
1109 1121 Image('/path/to/image.jpg')
1110 1122 Image(b'RAW_PNG_DATA...')
1111 1123
1112 1124 # Specifying Image(url=...) does not embed the image data,
1113 1125 # it only generates `<img>` tag with a link to the source.
1114 1126 # This will not work in the qtconsole or offline.
1115 1127 Image(url='http://www.google.fr/images/srpr/logo3w.png')
1116 1128
1117 1129 """
1118 1130 if filename is not None:
1119 1131 ext = self._find_ext(filename)
1120 1132 elif url is not None:
1121 1133 ext = self._find_ext(url)
1122 1134 elif data is None:
1123 1135 raise ValueError("No image data found. Expecting filename, url, or data.")
1124 1136 elif isinstance(data, str) and (
1125 1137 data.startswith('http') or _safe_exists(data)
1126 1138 ):
1127 1139 ext = self._find_ext(data)
1128 1140 else:
1129 1141 ext = None
1130 1142
1131 1143 if format is None:
1132 1144 if ext is not None:
1133 1145 if ext == u'jpg' or ext == u'jpeg':
1134 1146 format = self._FMT_JPEG
1135 1147 elif ext == u'png':
1136 1148 format = self._FMT_PNG
1137 1149 elif ext == u'gif':
1138 1150 format = self._FMT_GIF
1139 1151 else:
1140 1152 format = ext.lower()
1141 1153 elif isinstance(data, bytes):
1142 1154 # infer image type from image data header,
1143 1155 # only if format has not been specified.
1144 1156 if data[:2] == _JPEG:
1145 1157 format = self._FMT_JPEG
1146 1158
1147 1159 # failed to detect format, default png
1148 1160 if format is None:
1149 1161 format = self._FMT_PNG
1150 1162
1151 1163 if format.lower() == 'jpg':
1152 1164 # jpg->jpeg
1153 1165 format = self._FMT_JPEG
1154 1166
1155 1167 self.format = format.lower()
1156 1168 self.embed = embed if embed is not None else (url is None)
1157 1169
1158 1170 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
1159 1171 raise ValueError("Cannot embed the '%s' image format" % (self.format))
1160 1172 if self.embed:
1161 1173 self._mimetype = self._MIMETYPES.get(self.format)
1162 1174
1163 1175 self.width = width
1164 1176 self.height = height
1165 1177 self.retina = retina
1166 1178 self.unconfined = unconfined
1167 1179 super(Image, self).__init__(data=data, url=url, filename=filename,
1168 1180 metadata=metadata)
1169 1181
1170 1182 if self.width is None and self.metadata.get('width', {}):
1171 1183 self.width = metadata['width']
1172 1184
1173 1185 if self.height is None and self.metadata.get('height', {}):
1174 1186 self.height = metadata['height']
1175 1187
1176 1188 if retina:
1177 1189 self._retina_shape()
1178 1190
1179 1191
1180 1192 def _retina_shape(self):
1181 1193 """load pixel-doubled width and height from image data"""
1182 1194 if not self.embed:
1183 1195 return
1184 1196 if self.format == self._FMT_PNG:
1185 1197 w, h = _pngxy(self.data)
1186 1198 elif self.format == self._FMT_JPEG:
1187 1199 w, h = _jpegxy(self.data)
1188 1200 elif self.format == self._FMT_GIF:
1189 1201 w, h = _gifxy(self.data)
1190 1202 else:
1191 1203 # retina only supports png
1192 1204 return
1193 1205 self.width = w // 2
1194 1206 self.height = h // 2
1195 1207
1196 1208 def reload(self):
1197 1209 """Reload the raw data from file or URL."""
1198 1210 if self.embed:
1199 1211 super(Image,self).reload()
1200 1212 if self.retina:
1201 1213 self._retina_shape()
1202 1214
1203 1215 def _repr_html_(self):
1204 1216 if not self.embed:
1205 1217 width = height = klass = ''
1206 1218 if self.width:
1207 1219 width = ' width="%d"' % self.width
1208 1220 if self.height:
1209 1221 height = ' height="%d"' % self.height
1210 1222 if self.unconfined:
1211 1223 klass = ' class="unconfined"'
1212 1224 return u'<img src="{url}"{width}{height}{klass}/>'.format(
1213 1225 url=self.url,
1214 1226 width=width,
1215 1227 height=height,
1216 1228 klass=klass,
1217 1229 )
1218 1230
1219 1231 def _repr_mimebundle_(self, include=None, exclude=None):
1220 1232 """Return the image as a mimebundle
1221 1233
1222 1234 Any new mimetype support should be implemented here.
1223 1235 """
1224 1236 if self.embed:
1225 1237 mimetype = self._mimetype
1226 1238 data, metadata = self._data_and_metadata(always_both=True)
1227 1239 if metadata:
1228 1240 metadata = {mimetype: metadata}
1229 1241 return {mimetype: data}, metadata
1230 1242 else:
1231 1243 return {'text/html': self._repr_html_()}
1232 1244
1233 1245 def _data_and_metadata(self, always_both=False):
1234 1246 """shortcut for returning metadata with shape information, if defined"""
1235 1247 b64_data = b2a_base64(self.data).decode('ascii')
1236 1248 md = {}
1237 1249 if self.metadata:
1238 1250 md.update(self.metadata)
1239 1251 if self.width:
1240 1252 md['width'] = self.width
1241 1253 if self.height:
1242 1254 md['height'] = self.height
1243 1255 if self.unconfined:
1244 1256 md['unconfined'] = self.unconfined
1245 1257 if md or always_both:
1246 1258 return b64_data, md
1247 1259 else:
1248 1260 return b64_data
1249 1261
1250 1262 def _repr_png_(self):
1251 1263 if self.embed and self.format == self._FMT_PNG:
1252 1264 return self._data_and_metadata()
1253 1265
1254 1266 def _repr_jpeg_(self):
1255 1267 if self.embed and self.format == self._FMT_JPEG:
1256 1268 return self._data_and_metadata()
1257 1269
1258 1270 def _find_ext(self, s):
1259 1271 return s.split('.')[-1].lower()
1260 1272
1261 1273
1262 1274 class Video(DisplayObject):
1263 1275
1264 1276 def __init__(self, data=None, url=None, filename=None, embed=False, mimetype=None):
1265 1277 """Create a video object given raw data or an URL.
1266 1278
1267 1279 When this object is returned by an input cell or passed to the
1268 1280 display function, it will result in the video being displayed
1269 1281 in the frontend.
1270 1282
1271 1283 Parameters
1272 1284 ----------
1273 1285 data : unicode, str or bytes
1274 1286 The raw video data or a URL or filename to load the data from.
1275 1287 Raw data will require passing `embed=True`.
1276 1288 url : unicode
1277 1289 A URL for the video. If you specify `url=`,
1278 1290 the image data will not be embedded.
1279 1291 filename : unicode
1280 1292 Path to a local file containing the video.
1281 1293 Will be interpreted as a local URL unless `embed=True`.
1282 1294 embed : bool
1283 1295 Should the video be embedded using a data URI (True) or be
1284 1296 loaded using a <video> tag (False).
1285 1297
1286 1298 Since videos are large, embedding them should be avoided, if possible.
1287 1299 You must confirm embedding as your intention by passing `embed=True`.
1288 1300
1289 1301 Local files can be displayed with URLs without embedding the content, via::
1290 1302
1291 1303 Video('./video.mp4')
1292 1304
1293 1305 mimetype: unicode
1294 1306 Specify the mimetype for embedded videos.
1295 1307 Default will be guessed from file extension, if available.
1296 1308
1297 1309 Examples
1298 1310 --------
1299 1311
1300 1312 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1301 1313 Video('path/to/video.mp4')
1302 1314 Video('path/to/video.mp4', embed=True)
1303 1315 Video(b'raw-videodata', embed=True)
1304 1316 """
1305 1317 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1306 1318 url = data
1307 1319 data = None
1308 1320 elif os.path.exists(data):
1309 1321 filename = data
1310 1322 data = None
1311 1323
1312 1324 if data and not embed:
1313 1325 msg = ''.join([
1314 1326 "To embed videos, you must pass embed=True ",
1315 1327 "(this may make your notebook files huge)\n",
1316 1328 "Consider passing Video(url='...')",
1317 1329 ])
1318 1330 raise ValueError(msg)
1319 1331
1320 1332 self.mimetype = mimetype
1321 1333 self.embed = embed
1322 1334 super(Video, self).__init__(data=data, url=url, filename=filename)
1323 1335
1324 1336 def _repr_html_(self):
1325 1337 # External URLs and potentially local files are not embedded into the
1326 1338 # notebook output.
1327 1339 if not self.embed:
1328 1340 url = self.url if self.url is not None else self.filename
1329 1341 output = """<video src="{0}" controls>
1330 1342 Your browser does not support the <code>video</code> element.
1331 1343 </video>""".format(url)
1332 1344 return output
1333 1345
1334 1346 # Embedded videos are base64-encoded.
1335 1347 mimetype = self.mimetype
1336 1348 if self.filename is not None:
1337 1349 if not mimetype:
1338 1350 mimetype, _ = mimetypes.guess_type(self.filename)
1339 1351
1340 1352 with open(self.filename, 'rb') as f:
1341 1353 video = f.read()
1342 1354 else:
1343 1355 video = self.data
1344 1356 if isinstance(video, str):
1345 1357 # unicode input is already b64-encoded
1346 1358 b64_video = video
1347 1359 else:
1348 1360 b64_video = b2a_base64(video).decode('ascii').rstrip()
1349 1361
1350 1362 output = """<video controls>
1351 1363 <source src="data:{0};base64,{1}" type="{0}">
1352 1364 Your browser does not support the video tag.
1353 1365 </video>""".format(mimetype, b64_video)
1354 1366 return output
1355 1367
1356 1368 def reload(self):
1357 1369 # TODO
1358 1370 pass
1359 1371
1360 1372
1361 1373 def clear_output(wait=False):
1362 1374 """Clear the output of the current cell receiving output.
1363 1375
1364 1376 Parameters
1365 1377 ----------
1366 1378 wait : bool [default: false]
1367 1379 Wait to clear the output until new output is available to replace it."""
1368 1380 from IPython.core.interactiveshell import InteractiveShell
1369 1381 if InteractiveShell.initialized():
1370 1382 InteractiveShell.instance().display_pub.clear_output(wait)
1371 1383 else:
1372 1384 print('\033[2K\r', end='')
1373 1385 sys.stdout.flush()
1374 1386 print('\033[2K\r', end='')
1375 1387 sys.stderr.flush()
1376 1388
1377 1389
1378 1390 @skip_doctest
1379 1391 def set_matplotlib_formats(*formats, **kwargs):
1380 1392 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
1381 1393
1382 1394 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1383 1395
1384 1396 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1385 1397
1386 1398 To set this in your config files use the following::
1387 1399
1388 1400 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1389 1401 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1390 1402
1391 1403 Parameters
1392 1404 ----------
1393 1405 *formats : strs
1394 1406 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1395 1407 **kwargs :
1396 1408 Keyword args will be relayed to ``figure.canvas.print_figure``.
1397 1409 """
1398 1410 from IPython.core.interactiveshell import InteractiveShell
1399 1411 from IPython.core.pylabtools import select_figure_formats
1400 1412 # build kwargs, starting with InlineBackend config
1401 1413 kw = {}
1402 1414 from ipykernel.pylab.config import InlineBackend
1403 1415 cfg = InlineBackend.instance()
1404 1416 kw.update(cfg.print_figure_kwargs)
1405 1417 kw.update(**kwargs)
1406 1418 shell = InteractiveShell.instance()
1407 1419 select_figure_formats(shell, formats, **kw)
1408 1420
1409 1421 @skip_doctest
1410 1422 def set_matplotlib_close(close=True):
1411 1423 """Set whether the inline backend closes all figures automatically or not.
1412 1424
1413 1425 By default, the inline backend used in the IPython Notebook will close all
1414 1426 matplotlib figures automatically after each cell is run. This means that
1415 1427 plots in different cells won't interfere. Sometimes, you may want to make
1416 1428 a plot in one cell and then refine it in later cells. This can be accomplished
1417 1429 by::
1418 1430
1419 1431 In [1]: set_matplotlib_close(False)
1420 1432
1421 1433 To set this in your config files use the following::
1422 1434
1423 1435 c.InlineBackend.close_figures = False
1424 1436
1425 1437 Parameters
1426 1438 ----------
1427 1439 close : bool
1428 1440 Should all matplotlib figures be automatically closed after each cell is
1429 1441 run?
1430 1442 """
1431 1443 from ipykernel.pylab.config import InlineBackend
1432 1444 cfg = InlineBackend.instance()
1433 1445 cfg.close_figures = close
@@ -1,414 +1,421 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 @mock.patch('warnings.warn')
199 199 def test_encourage_iframe_over_html(m_warn):
200 display.HTML()
201 m_warn.assert_not_called()
202
200 203 display.HTML('<br />')
201 204 m_warn.assert_not_called()
202 205
203 206 display.HTML('<html><p>Lots of content here</p><iframe src="http://a.com"></iframe>')
204 207 m_warn.assert_not_called()
205 208
206 209 display.HTML('<iframe src="http://a.com"></iframe>')
207 210 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
208 211
212 m_warn.reset_mock()
213 display.HTML('<IFRAME SRC="http://a.com"></IFRAME>')
214 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
215
209 216 def test_progress():
210 217 p = display.ProgressBar(10)
211 218 nt.assert_in('0/10',repr(p))
212 219 p.html_width = '100%'
213 220 p.progress = 5
214 221 nt.assert_equal(p._repr_html_(), "<progress style='width:100%' max='10' value='5'></progress>")
215 222
216 223 def test_progress_iter():
217 224 with capture_output(display=False) as captured:
218 225 for i in display.ProgressBar(5):
219 226 out = captured.stdout
220 227 nt.assert_in('{0}/5'.format(i), out)
221 228 out = captured.stdout
222 229 nt.assert_in('5/5', out)
223 230
224 231 def test_json():
225 232 d = {'a': 5}
226 233 lis = [d]
227 234 metadata = [
228 235 {'expanded': False, 'root': 'root'},
229 236 {'expanded': True, 'root': 'root'},
230 237 {'expanded': False, 'root': 'custom'},
231 238 {'expanded': True, 'root': 'custom'},
232 239 ]
233 240 json_objs = [
234 241 display.JSON(d),
235 242 display.JSON(d, expanded=True),
236 243 display.JSON(d, root='custom'),
237 244 display.JSON(d, expanded=True, root='custom'),
238 245 ]
239 246 for j, md in zip(json_objs, metadata):
240 247 nt.assert_equal(j._repr_json_(), (d, md))
241 248
242 249 with warnings.catch_warnings(record=True) as w:
243 250 warnings.simplefilter("always")
244 251 j = display.JSON(json.dumps(d))
245 252 nt.assert_equal(len(w), 1)
246 253 nt.assert_equal(j._repr_json_(), (d, metadata[0]))
247 254
248 255 json_objs = [
249 256 display.JSON(lis),
250 257 display.JSON(lis, expanded=True),
251 258 display.JSON(lis, root='custom'),
252 259 display.JSON(lis, expanded=True, root='custom'),
253 260 ]
254 261 for j, md in zip(json_objs, metadata):
255 262 nt.assert_equal(j._repr_json_(), (lis, md))
256 263
257 264 with warnings.catch_warnings(record=True) as w:
258 265 warnings.simplefilter("always")
259 266 j = display.JSON(json.dumps(lis))
260 267 nt.assert_equal(len(w), 1)
261 268 nt.assert_equal(j._repr_json_(), (lis, metadata[0]))
262 269
263 270 def test_video_embedding():
264 271 """use a tempfile, with dummy-data, to ensure that video embedding doesn't crash"""
265 272 v = display.Video("http://ignored")
266 273 assert not v.embed
267 274 html = v._repr_html_()
268 275 nt.assert_not_in('src="data:', html)
269 276 nt.assert_in('src="http://ignored"', html)
270 277
271 278 with nt.assert_raises(ValueError):
272 279 v = display.Video(b'abc')
273 280
274 281 with NamedFileInTemporaryDirectory('test.mp4') as f:
275 282 f.write(b'abc')
276 283 f.close()
277 284
278 285 v = display.Video(f.name)
279 286 assert not v.embed
280 287 html = v._repr_html_()
281 288 nt.assert_not_in('src="data:', html)
282 289
283 290 v = display.Video(f.name, embed=True)
284 291 html = v._repr_html_()
285 292 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
286 293
287 294 v = display.Video(f.name, embed=True, mimetype='video/other')
288 295 html = v._repr_html_()
289 296 nt.assert_in('src="data:video/other;base64,YWJj"',html)
290 297
291 298 v = display.Video(b'abc', embed=True, mimetype='video/mp4')
292 299 html = v._repr_html_()
293 300 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
294 301
295 302 v = display.Video(u'YWJj', embed=True, mimetype='video/xyz')
296 303 html = v._repr_html_()
297 304 nt.assert_in('src="data:video/xyz;base64,YWJj"',html)
298 305
299 306 def test_html_metadata():
300 307 s = "<h1>Test</h1>"
301 308 h = display.HTML(s, metadata={"isolated": True})
302 309 nt.assert_equal(h._repr_html_(), (s, {"isolated": True}))
303 310
304 311 def test_display_id():
305 312 ip = get_ipython()
306 313 with mock.patch.object(ip.display_pub, 'publish') as pub:
307 314 handle = display.display('x')
308 315 nt.assert_is(handle, None)
309 316 handle = display.display('y', display_id='secret')
310 317 nt.assert_is_instance(handle, display.DisplayHandle)
311 318 handle2 = display.display('z', display_id=True)
312 319 nt.assert_is_instance(handle2, display.DisplayHandle)
313 320 nt.assert_not_equal(handle.display_id, handle2.display_id)
314 321
315 322 nt.assert_equal(pub.call_count, 3)
316 323 args, kwargs = pub.call_args_list[0]
317 324 nt.assert_equal(args, ())
318 325 nt.assert_equal(kwargs, {
319 326 'data': {
320 327 'text/plain': repr('x')
321 328 },
322 329 'metadata': {},
323 330 })
324 331 args, kwargs = pub.call_args_list[1]
325 332 nt.assert_equal(args, ())
326 333 nt.assert_equal(kwargs, {
327 334 'data': {
328 335 'text/plain': repr('y')
329 336 },
330 337 'metadata': {},
331 338 'transient': {
332 339 'display_id': handle.display_id,
333 340 },
334 341 })
335 342 args, kwargs = pub.call_args_list[2]
336 343 nt.assert_equal(args, ())
337 344 nt.assert_equal(kwargs, {
338 345 'data': {
339 346 'text/plain': repr('z')
340 347 },
341 348 'metadata': {},
342 349 'transient': {
343 350 'display_id': handle2.display_id,
344 351 },
345 352 })
346 353
347 354
348 355 def test_update_display():
349 356 ip = get_ipython()
350 357 with mock.patch.object(ip.display_pub, 'publish') as pub:
351 358 with nt.assert_raises(TypeError):
352 359 display.update_display('x')
353 360 display.update_display('x', display_id='1')
354 361 display.update_display('y', display_id='2')
355 362 args, kwargs = pub.call_args_list[0]
356 363 nt.assert_equal(args, ())
357 364 nt.assert_equal(kwargs, {
358 365 'data': {
359 366 'text/plain': repr('x')
360 367 },
361 368 'metadata': {},
362 369 'transient': {
363 370 'display_id': '1',
364 371 },
365 372 'update': True,
366 373 })
367 374 args, kwargs = pub.call_args_list[1]
368 375 nt.assert_equal(args, ())
369 376 nt.assert_equal(kwargs, {
370 377 'data': {
371 378 'text/plain': repr('y')
372 379 },
373 380 'metadata': {},
374 381 'transient': {
375 382 'display_id': '2',
376 383 },
377 384 'update': True,
378 385 })
379 386
380 387
381 388 def test_display_handle():
382 389 ip = get_ipython()
383 390 handle = display.DisplayHandle()
384 391 nt.assert_is_instance(handle.display_id, str)
385 392 handle = display.DisplayHandle('my-id')
386 393 nt.assert_equal(handle.display_id, 'my-id')
387 394 with mock.patch.object(ip.display_pub, 'publish') as pub:
388 395 handle.display('x')
389 396 handle.update('y')
390 397
391 398 args, kwargs = pub.call_args_list[0]
392 399 nt.assert_equal(args, ())
393 400 nt.assert_equal(kwargs, {
394 401 'data': {
395 402 'text/plain': repr('x')
396 403 },
397 404 'metadata': {},
398 405 'transient': {
399 406 'display_id': handle.display_id,
400 407 }
401 408 })
402 409 args, kwargs = pub.call_args_list[1]
403 410 nt.assert_equal(args, ())
404 411 nt.assert_equal(kwargs, {
405 412 'data': {
406 413 'text/plain': repr('y')
407 414 },
408 415 'metadata': {},
409 416 'transient': {
410 417 'display_id': handle.display_id,
411 418 },
412 419 'update': True,
413 420 })
414 421
General Comments 0
You need to be logged in to leave comments. Login now