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