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