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