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