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