##// END OF EJS Templates
back out metadata copying for every mime-type
Paul Ivanov -
Show More
@@ -1,685 +1,679 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Top-level display functions for displaying object in different formats.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2013 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 from __future__ import print_function
21 21
22 22 import os
23 23 import struct
24 24
25 25 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
26 26 unicode_type)
27 27
28 28 from .displaypub import publish_display_data
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # utility functions
32 32 #-----------------------------------------------------------------------------
33 33
34 34 def _safe_exists(path):
35 35 """Check path, but don't let exceptions raise"""
36 36 try:
37 37 return os.path.exists(path)
38 38 except Exception:
39 39 return False
40 40
41 41 def _merge(d1, d2):
42 42 """Like update, but merges sub-dicts instead of clobbering at the top level.
43 43
44 44 Updates d1 in-place
45 45 """
46 46
47 47 if not isinstance(d2, dict) or not isinstance(d1, dict):
48 48 return d2
49 49 for key, value in d2.items():
50 50 d1[key] = _merge(d1.get(key), value)
51 51 return d1
52 52
53 53 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
54 54 """internal implementation of all display_foo methods
55 55
56 56 Parameters
57 57 ----------
58 58 mimetype : str
59 59 The mimetype to be published (e.g. 'image/png')
60 60 objs : tuple of objects
61 61 The Python objects to display, or if raw=True raw text data to
62 62 display.
63 63 raw : bool
64 64 Are the data objects raw data or Python objects that need to be
65 65 formatted before display? [default: False]
66 66 metadata : dict (optional)
67 67 Metadata to be associated with the specific mimetype output.
68 68 """
69 69 if metadata:
70 70 metadata = {mimetype: metadata}
71 71 if raw:
72 72 # turn list of pngdata into list of { 'image/png': pngdata }
73 73 objs = [ {mimetype: obj} for obj in objs ]
74 74 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
75 75
76 76 #-----------------------------------------------------------------------------
77 77 # Main functions
78 78 #-----------------------------------------------------------------------------
79 79
80 80 def display(*objs, **kwargs):
81 81 """Display a Python object in all frontends.
82 82
83 83 By default all representations will be computed and sent to the frontends.
84 84 Frontends can decide which representation is used and how.
85 85
86 86 Parameters
87 87 ----------
88 88 objs : tuple of objects
89 89 The Python objects to display.
90 90 raw : bool, optional
91 91 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
92 92 or Python objects that need to be formatted before display? [default: False]
93 93 include : list or tuple, optional
94 94 A list of format type strings (MIME types) to include in the
95 95 format data dict. If this is set *only* the format types included
96 96 in this list will be computed.
97 97 exclude : list or tuple, optional
98 98 A list of format type strings (MIME types) to exclude in the format
99 99 data dict. If this is set all format types will be computed,
100 100 except for those included in this argument.
101 101 metadata : dict, optional
102 102 A dictionary of metadata to associate with the output.
103 103 mime-type keys in this dictionary will be associated with the individual
104 104 representation formats, if they exist.
105 105 """
106 106 raw = kwargs.get('raw', False)
107 107 include = kwargs.get('include')
108 108 exclude = kwargs.get('exclude')
109 109 metadata = kwargs.get('metadata')
110 110
111 111 from IPython.core.interactiveshell import InteractiveShell
112 112
113 113 if raw:
114 114 for obj in objs:
115 115 publish_display_data('display', obj, metadata)
116 116 else:
117 117 format = InteractiveShell.instance().display_formatter.format
118 118 for obj in objs:
119 119 format_dict, md_dict = format(obj, include=include, exclude=exclude)
120 120 if metadata:
121 # publish_display_data exepects metadata dict that is keyed by
122 # mimetype, so this ensures we recreate such a dict for it, if
123 # one is not already passed to us
124 if set(metadata.keys()) != set(format_dict.keys()):
125 _md = metadata.copy()
126 metadata = {mt: _md for mt in format_dict}
127 121 # kwarg-specified metadata gets precedence
128 122 _merge(md_dict, metadata)
129 123 publish_display_data('display', format_dict, md_dict)
130 124
131 125
132 126 def display_pretty(*objs, **kwargs):
133 127 """Display the pretty (default) representation of an object.
134 128
135 129 Parameters
136 130 ----------
137 131 objs : tuple of objects
138 132 The Python objects to display, or if raw=True raw text data to
139 133 display.
140 134 raw : bool
141 135 Are the data objects raw data or Python objects that need to be
142 136 formatted before display? [default: False]
143 137 metadata : dict (optional)
144 138 Metadata to be associated with the specific mimetype output.
145 139 """
146 140 _display_mimetype('text/plain', objs, **kwargs)
147 141
148 142
149 143 def display_html(*objs, **kwargs):
150 144 """Display the HTML representation of an object.
151 145
152 146 Parameters
153 147 ----------
154 148 objs : tuple of objects
155 149 The Python objects to display, or if raw=True raw HTML data to
156 150 display.
157 151 raw : bool
158 152 Are the data objects raw data or Python objects that need to be
159 153 formatted before display? [default: False]
160 154 metadata : dict (optional)
161 155 Metadata to be associated with the specific mimetype output.
162 156 """
163 157 _display_mimetype('text/html', objs, **kwargs)
164 158
165 159
166 160 def display_svg(*objs, **kwargs):
167 161 """Display the SVG representation of an object.
168 162
169 163 Parameters
170 164 ----------
171 165 objs : tuple of objects
172 166 The Python objects to display, or if raw=True raw svg data to
173 167 display.
174 168 raw : bool
175 169 Are the data objects raw data or Python objects that need to be
176 170 formatted before display? [default: False]
177 171 metadata : dict (optional)
178 172 Metadata to be associated with the specific mimetype output.
179 173 """
180 174 _display_mimetype('image/svg+xml', objs, **kwargs)
181 175
182 176
183 177 def display_png(*objs, **kwargs):
184 178 """Display the PNG representation of an object.
185 179
186 180 Parameters
187 181 ----------
188 182 objs : tuple of objects
189 183 The Python objects to display, or if raw=True raw png data to
190 184 display.
191 185 raw : bool
192 186 Are the data objects raw data or Python objects that need to be
193 187 formatted before display? [default: False]
194 188 metadata : dict (optional)
195 189 Metadata to be associated with the specific mimetype output.
196 190 """
197 191 _display_mimetype('image/png', objs, **kwargs)
198 192
199 193
200 194 def display_jpeg(*objs, **kwargs):
201 195 """Display the JPEG representation of an object.
202 196
203 197 Parameters
204 198 ----------
205 199 objs : tuple of objects
206 200 The Python objects to display, or if raw=True raw JPEG data to
207 201 display.
208 202 raw : bool
209 203 Are the data objects raw data or Python objects that need to be
210 204 formatted before display? [default: False]
211 205 metadata : dict (optional)
212 206 Metadata to be associated with the specific mimetype output.
213 207 """
214 208 _display_mimetype('image/jpeg', objs, **kwargs)
215 209
216 210
217 211 def display_latex(*objs, **kwargs):
218 212 """Display the LaTeX representation of an object.
219 213
220 214 Parameters
221 215 ----------
222 216 objs : tuple of objects
223 217 The Python objects to display, or if raw=True raw latex data to
224 218 display.
225 219 raw : bool
226 220 Are the data objects raw data or Python objects that need to be
227 221 formatted before display? [default: False]
228 222 metadata : dict (optional)
229 223 Metadata to be associated with the specific mimetype output.
230 224 """
231 225 _display_mimetype('text/latex', objs, **kwargs)
232 226
233 227
234 228 def display_json(*objs, **kwargs):
235 229 """Display the JSON representation of an object.
236 230
237 231 Note that not many frontends support displaying JSON.
238 232
239 233 Parameters
240 234 ----------
241 235 objs : tuple of objects
242 236 The Python objects to display, or if raw=True raw json data to
243 237 display.
244 238 raw : bool
245 239 Are the data objects raw data or Python objects that need to be
246 240 formatted before display? [default: False]
247 241 metadata : dict (optional)
248 242 Metadata to be associated with the specific mimetype output.
249 243 """
250 244 _display_mimetype('application/json', objs, **kwargs)
251 245
252 246
253 247 def display_javascript(*objs, **kwargs):
254 248 """Display the Javascript representation of an object.
255 249
256 250 Parameters
257 251 ----------
258 252 objs : tuple of objects
259 253 The Python objects to display, or if raw=True raw javascript data to
260 254 display.
261 255 raw : bool
262 256 Are the data objects raw data or Python objects that need to be
263 257 formatted before display? [default: False]
264 258 metadata : dict (optional)
265 259 Metadata to be associated with the specific mimetype output.
266 260 """
267 261 _display_mimetype('application/javascript', objs, **kwargs)
268 262
269 263 #-----------------------------------------------------------------------------
270 264 # Smart classes
271 265 #-----------------------------------------------------------------------------
272 266
273 267
274 268 class DisplayObject(object):
275 269 """An object that wraps data to be displayed."""
276 270
277 271 _read_flags = 'r'
278 272
279 273 def __init__(self, data=None, url=None, filename=None):
280 274 """Create a display object given raw data.
281 275
282 276 When this object is returned by an expression or passed to the
283 277 display function, it will result in the data being displayed
284 278 in the frontend. The MIME type of the data should match the
285 279 subclasses used, so the Png subclass should be used for 'image/png'
286 280 data. If the data is a URL, the data will first be downloaded
287 281 and then displayed. If
288 282
289 283 Parameters
290 284 ----------
291 285 data : unicode, str or bytes
292 286 The raw data or a URL or file to load the data from
293 287 url : unicode
294 288 A URL to download the data from.
295 289 filename : unicode
296 290 Path to a local file to load the data from.
297 291 """
298 292 if data is not None and isinstance(data, string_types):
299 293 if data.startswith('http') and url is None:
300 294 url = data
301 295 filename = None
302 296 data = None
303 297 elif _safe_exists(data) and filename is None:
304 298 url = None
305 299 filename = data
306 300 data = None
307 301
308 302 self.data = data
309 303 self.url = url
310 304 self.filename = None if filename is None else unicode_type(filename)
311 305
312 306 self.reload()
313 307
314 308 def reload(self):
315 309 """Reload the raw data from file or URL."""
316 310 if self.filename is not None:
317 311 with open(self.filename, self._read_flags) as f:
318 312 self.data = f.read()
319 313 elif self.url is not None:
320 314 try:
321 315 try:
322 316 from urllib.request import urlopen # Py3
323 317 except ImportError:
324 318 from urllib2 import urlopen
325 319 response = urlopen(self.url)
326 320 self.data = response.read()
327 321 # extract encoding from header, if there is one:
328 322 encoding = None
329 323 for sub in response.headers['content-type'].split(';'):
330 324 sub = sub.strip()
331 325 if sub.startswith('charset'):
332 326 encoding = sub.split('=')[-1].strip()
333 327 break
334 328 # decode data, if an encoding was specified
335 329 if encoding:
336 330 self.data = self.data.decode(encoding, 'replace')
337 331 except:
338 332 self.data = None
339 333
340 334 class Pretty(DisplayObject):
341 335
342 336 def _repr_pretty_(self):
343 337 return self.data
344 338
345 339
346 340 class HTML(DisplayObject):
347 341
348 342 def _repr_html_(self):
349 343 return self.data
350 344
351 345 def __html__(self):
352 346 """
353 347 This method exists to inform other HTML-using modules (e.g. Markupsafe,
354 348 htmltag, etc) that this object is HTML and does not need things like
355 349 special characters (<>&) escaped.
356 350 """
357 351 return self._repr_html_()
358 352
359 353
360 354 class Math(DisplayObject):
361 355
362 356 def _repr_latex_(self):
363 357 s = self.data.strip('$')
364 358 return "$$%s$$" % s
365 359
366 360
367 361 class Latex(DisplayObject):
368 362
369 363 def _repr_latex_(self):
370 364 return self.data
371 365
372 366
373 367 class SVG(DisplayObject):
374 368
375 369 # wrap data in a property, which extracts the <svg> tag, discarding
376 370 # document headers
377 371 _data = None
378 372
379 373 @property
380 374 def data(self):
381 375 return self._data
382 376
383 377 @data.setter
384 378 def data(self, svg):
385 379 if svg is None:
386 380 self._data = None
387 381 return
388 382 # parse into dom object
389 383 from xml.dom import minidom
390 384 svg = cast_bytes_py2(svg)
391 385 x = minidom.parseString(svg)
392 386 # get svg tag (should be 1)
393 387 found_svg = x.getElementsByTagName('svg')
394 388 if found_svg:
395 389 svg = found_svg[0].toxml()
396 390 else:
397 391 # fallback on the input, trust the user
398 392 # but this is probably an error.
399 393 pass
400 394 svg = cast_unicode(svg)
401 395 self._data = svg
402 396
403 397 def _repr_svg_(self):
404 398 return self.data
405 399
406 400
407 401 class JSON(DisplayObject):
408 402
409 403 def _repr_json_(self):
410 404 return self.data
411 405
412 406 css_t = """$("head").append($("<link/>").attr({
413 407 rel: "stylesheet",
414 408 type: "text/css",
415 409 href: "%s"
416 410 }));
417 411 """
418 412
419 413 lib_t1 = """$.getScript("%s", function () {
420 414 """
421 415 lib_t2 = """});
422 416 """
423 417
424 418 class Javascript(DisplayObject):
425 419
426 420 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
427 421 """Create a Javascript display object given raw data.
428 422
429 423 When this object is returned by an expression or passed to the
430 424 display function, it will result in the data being displayed
431 425 in the frontend. If the data is a URL, the data will first be
432 426 downloaded and then displayed.
433 427
434 428 In the Notebook, the containing element will be available as `element`,
435 429 and jQuery will be available. The output area starts hidden, so if
436 430 the js appends content to `element` that should be visible, then
437 431 it must call `container.show()` to unhide the area.
438 432
439 433 Parameters
440 434 ----------
441 435 data : unicode, str or bytes
442 436 The Javascript source code or a URL to download it from.
443 437 url : unicode
444 438 A URL to download the data from.
445 439 filename : unicode
446 440 Path to a local file to load the data from.
447 441 lib : list or str
448 442 A sequence of Javascript library URLs to load asynchronously before
449 443 running the source code. The full URLs of the libraries should
450 444 be given. A single Javascript library URL can also be given as a
451 445 string.
452 446 css: : list or str
453 447 A sequence of css files to load before running the source code.
454 448 The full URLs of the css files should be given. A single css URL
455 449 can also be given as a string.
456 450 """
457 451 if isinstance(lib, string_types):
458 452 lib = [lib]
459 453 elif lib is None:
460 454 lib = []
461 455 if isinstance(css, string_types):
462 456 css = [css]
463 457 elif css is None:
464 458 css = []
465 459 if not isinstance(lib, (list,tuple)):
466 460 raise TypeError('expected sequence, got: %r' % lib)
467 461 if not isinstance(css, (list,tuple)):
468 462 raise TypeError('expected sequence, got: %r' % css)
469 463 self.lib = lib
470 464 self.css = css
471 465 super(Javascript, self).__init__(data=data, url=url, filename=filename)
472 466
473 467 def _repr_javascript_(self):
474 468 r = ''
475 469 for c in self.css:
476 470 r += css_t % c
477 471 for l in self.lib:
478 472 r += lib_t1 % l
479 473 r += self.data
480 474 r += lib_t2*len(self.lib)
481 475 return r
482 476
483 477 # constants for identifying png/jpeg data
484 478 _PNG = b'\x89PNG\r\n\x1a\n'
485 479 _JPEG = b'\xff\xd8'
486 480
487 481 def _pngxy(data):
488 482 """read the (width, height) from a PNG header"""
489 483 ihdr = data.index(b'IHDR')
490 484 # next 8 bytes are width/height
491 485 w4h4 = data[ihdr+4:ihdr+12]
492 486 return struct.unpack('>ii', w4h4)
493 487
494 488 def _jpegxy(data):
495 489 """read the (width, height) from a JPEG header"""
496 490 # adapted from http://www.64lines.com/jpeg-width-height
497 491
498 492 idx = 4
499 493 while True:
500 494 block_size = struct.unpack('>H', data[idx:idx+2])[0]
501 495 idx = idx + block_size
502 496 if data[idx:idx+2] == b'\xFF\xC0':
503 497 # found Start of Frame
504 498 iSOF = idx
505 499 break
506 500 else:
507 501 # read another block
508 502 idx += 2
509 503
510 504 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
511 505 return w, h
512 506
513 507 class Image(DisplayObject):
514 508
515 509 _read_flags = 'rb'
516 510 _FMT_JPEG = u'jpeg'
517 511 _FMT_PNG = u'png'
518 512 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
519 513
520 514 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
521 515 """Create a PNG/JPEG image object given raw data.
522 516
523 517 When this object is returned by an input cell or passed to the
524 518 display function, it will result in the image being displayed
525 519 in the frontend.
526 520
527 521 Parameters
528 522 ----------
529 523 data : unicode, str or bytes
530 524 The raw image data or a URL or filename to load the data from.
531 525 This always results in embedded image data.
532 526 url : unicode
533 527 A URL to download the data from. If you specify `url=`,
534 528 the image data will not be embedded unless you also specify `embed=True`.
535 529 filename : unicode
536 530 Path to a local file to load the data from.
537 531 Images from a file are always embedded.
538 532 format : unicode
539 533 The format of the image data (png/jpeg/jpg). If a filename or URL is given
540 534 for format will be inferred from the filename extension.
541 535 embed : bool
542 536 Should the image data be embedded using a data URI (True) or be
543 537 loaded using an <img> tag. Set this to True if you want the image
544 538 to be viewable later with no internet connection in the notebook.
545 539
546 540 Default is `True`, unless the keyword argument `url` is set, then
547 541 default value is `False`.
548 542
549 543 Note that QtConsole is not able to display images if `embed` is set to `False`
550 544 width : int
551 545 Width to which to constrain the image in html
552 546 height : int
553 547 Height to which to constrain the image in html
554 548 retina : bool
555 549 Automatically set the width and height to half of the measured
556 550 width and height.
557 551 This only works for embedded images because it reads the width/height
558 552 from image data.
559 553 For non-embedded images, you can just set the desired display width
560 554 and height directly.
561 555
562 556 Examples
563 557 --------
564 558 # embedded image data, works in qtconsole and notebook
565 559 # when passed positionally, the first arg can be any of raw image data,
566 560 # a URL, or a filename from which to load image data.
567 561 # The result is always embedding image data for inline images.
568 562 Image('http://www.google.fr/images/srpr/logo3w.png')
569 563 Image('/path/to/image.jpg')
570 564 Image(b'RAW_PNG_DATA...')
571 565
572 566 # Specifying Image(url=...) does not embed the image data,
573 567 # it only generates `<img>` tag with a link to the source.
574 568 # This will not work in the qtconsole or offline.
575 569 Image(url='http://www.google.fr/images/srpr/logo3w.png')
576 570
577 571 """
578 572 if filename is not None:
579 573 ext = self._find_ext(filename)
580 574 elif url is not None:
581 575 ext = self._find_ext(url)
582 576 elif data is None:
583 577 raise ValueError("No image data found. Expecting filename, url, or data.")
584 578 elif isinstance(data, string_types) and (
585 579 data.startswith('http') or _safe_exists(data)
586 580 ):
587 581 ext = self._find_ext(data)
588 582 else:
589 583 ext = None
590 584
591 585 if ext is not None:
592 586 format = ext.lower()
593 587 if ext == u'jpg' or ext == u'jpeg':
594 588 format = self._FMT_JPEG
595 589 if ext == u'png':
596 590 format = self._FMT_PNG
597 591 elif isinstance(data, bytes) and format == 'png':
598 592 # infer image type from image data header,
599 593 # only if format might not have been specified.
600 594 if data[:2] == _JPEG:
601 595 format = 'jpeg'
602 596
603 597 self.format = unicode_type(format).lower()
604 598 self.embed = embed if embed is not None else (url is None)
605 599
606 600 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
607 601 raise ValueError("Cannot embed the '%s' image format" % (self.format))
608 602 self.width = width
609 603 self.height = height
610 604 self.retina = retina
611 605 super(Image, self).__init__(data=data, url=url, filename=filename)
612 606
613 607 if retina:
614 608 self._retina_shape()
615 609
616 610 def _retina_shape(self):
617 611 """load pixel-doubled width and height from image data"""
618 612 if not self.embed:
619 613 return
620 614 if self.format == 'png':
621 615 w, h = _pngxy(self.data)
622 616 elif self.format == 'jpeg':
623 617 w, h = _jpegxy(self.data)
624 618 else:
625 619 # retina only supports png
626 620 return
627 621 self.width = w // 2
628 622 self.height = h // 2
629 623
630 624 def reload(self):
631 625 """Reload the raw data from file or URL."""
632 626 if self.embed:
633 627 super(Image,self).reload()
634 628 if self.retina:
635 629 self._retina_shape()
636 630
637 631 def _repr_html_(self):
638 632 if not self.embed:
639 633 width = height = ''
640 634 if self.width:
641 635 width = ' width="%d"' % self.width
642 636 if self.height:
643 637 height = ' height="%d"' % self.height
644 638 return u'<img src="%s"%s%s/>' % (self.url, width, height)
645 639
646 640 def _data_and_metadata(self):
647 641 """shortcut for returning metadata with shape information, if defined"""
648 642 md = {}
649 643 if self.width:
650 644 md['width'] = self.width
651 645 if self.height:
652 646 md['height'] = self.height
653 647 if md:
654 648 return self.data, md
655 649 else:
656 650 return self.data
657 651
658 652 def _repr_png_(self):
659 653 if self.embed and self.format == u'png':
660 654 return self._data_and_metadata()
661 655
662 656 def _repr_jpeg_(self):
663 657 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
664 658 return self._data_and_metadata()
665 659
666 660 def _find_ext(self, s):
667 661 return unicode_type(s.split('.')[-1].lower())
668 662
669 663
670 664 def clear_output(wait=False):
671 665 """Clear the output of the current cell receiving output.
672 666
673 667 Parameters
674 668 ----------
675 669 wait : bool [default: false]
676 670 Wait to clear the output until new output is available to replace it."""
677 671 from IPython.core.interactiveshell import InteractiveShell
678 672 if InteractiveShell.initialized():
679 673 InteractiveShell.instance().display_pub.clear_output(wait)
680 674 else:
681 675 from IPython.utils import io
682 676 print('\033[2K\r', file=io.stdout, end='')
683 677 io.stdout.flush()
684 678 print('\033[2K\r', file=io.stderr, end='')
685 679 io.stderr.flush()
General Comments 0
You need to be logged in to leave comments. Login now