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