##// END OF EJS Templates
Adding PDFFormatter and kernel side handling of PDF display data.
Brian E. Granger -
Show More
@@ -1,701 +1,719 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 not raw:
114 114 format = InteractiveShell.instance().display_formatter.format
115 115
116 116 for obj in objs:
117 117
118 118 # If _ipython_display_ is defined, use that to display this object.
119 119 display_method = getattr(obj, '_ipython_display_', None)
120 120 if display_method is not None:
121 121 try:
122 122 display_method(**kwargs)
123 123 except NotImplementedError:
124 124 pass
125 125 else:
126 126 continue
127 127 if raw:
128 128 publish_display_data('display', obj, metadata)
129 129 else:
130 130 format_dict, md_dict = format(obj, include=include, exclude=exclude)
131 131 if metadata:
132 132 # kwarg-specified metadata gets precedence
133 133 _merge(md_dict, metadata)
134 134 publish_display_data('display', format_dict, md_dict)
135 135
136 136
137 137 def display_pretty(*objs, **kwargs):
138 138 """Display the pretty (default) representation of an object.
139 139
140 140 Parameters
141 141 ----------
142 142 objs : tuple of objects
143 143 The Python objects to display, or if raw=True raw text data to
144 144 display.
145 145 raw : bool
146 146 Are the data objects raw data or Python objects that need to be
147 147 formatted before display? [default: False]
148 148 metadata : dict (optional)
149 149 Metadata to be associated with the specific mimetype output.
150 150 """
151 151 _display_mimetype('text/plain', objs, **kwargs)
152 152
153 153
154 154 def display_html(*objs, **kwargs):
155 155 """Display the HTML representation of an object.
156 156
157 157 Parameters
158 158 ----------
159 159 objs : tuple of objects
160 160 The Python objects to display, or if raw=True raw HTML data to
161 161 display.
162 162 raw : bool
163 163 Are the data objects raw data or Python objects that need to be
164 164 formatted before display? [default: False]
165 165 metadata : dict (optional)
166 166 Metadata to be associated with the specific mimetype output.
167 167 """
168 168 _display_mimetype('text/html', objs, **kwargs)
169 169
170 170
171 171 def display_svg(*objs, **kwargs):
172 172 """Display the SVG representation of an object.
173 173
174 174 Parameters
175 175 ----------
176 176 objs : tuple of objects
177 177 The Python objects to display, or if raw=True raw svg data to
178 178 display.
179 179 raw : bool
180 180 Are the data objects raw data or Python objects that need to be
181 181 formatted before display? [default: False]
182 182 metadata : dict (optional)
183 183 Metadata to be associated with the specific mimetype output.
184 184 """
185 185 _display_mimetype('image/svg+xml', objs, **kwargs)
186 186
187 187
188 188 def display_png(*objs, **kwargs):
189 189 """Display the PNG representation of an object.
190 190
191 191 Parameters
192 192 ----------
193 193 objs : tuple of objects
194 194 The Python objects to display, or if raw=True raw png data to
195 195 display.
196 196 raw : bool
197 197 Are the data objects raw data or Python objects that need to be
198 198 formatted before display? [default: False]
199 199 metadata : dict (optional)
200 200 Metadata to be associated with the specific mimetype output.
201 201 """
202 202 _display_mimetype('image/png', objs, **kwargs)
203 203
204 204
205 205 def display_jpeg(*objs, **kwargs):
206 206 """Display the JPEG representation of an object.
207 207
208 208 Parameters
209 209 ----------
210 210 objs : tuple of objects
211 211 The Python objects to display, or if raw=True raw JPEG data to
212 212 display.
213 213 raw : bool
214 214 Are the data objects raw data or Python objects that need to be
215 215 formatted before display? [default: False]
216 216 metadata : dict (optional)
217 217 Metadata to be associated with the specific mimetype output.
218 218 """
219 219 _display_mimetype('image/jpeg', objs, **kwargs)
220 220
221 221
222 222 def display_latex(*objs, **kwargs):
223 223 """Display the LaTeX representation of an object.
224 224
225 225 Parameters
226 226 ----------
227 227 objs : tuple of objects
228 228 The Python objects to display, or if raw=True raw latex data to
229 229 display.
230 230 raw : bool
231 231 Are the data objects raw data or Python objects that need to be
232 232 formatted before display? [default: False]
233 233 metadata : dict (optional)
234 234 Metadata to be associated with the specific mimetype output.
235 235 """
236 236 _display_mimetype('text/latex', objs, **kwargs)
237 237
238 238
239 239 def display_json(*objs, **kwargs):
240 240 """Display the JSON representation of an object.
241 241
242 242 Note that not many frontends support displaying JSON.
243 243
244 244 Parameters
245 245 ----------
246 246 objs : tuple of objects
247 247 The Python objects to display, or if raw=True raw json data to
248 248 display.
249 249 raw : bool
250 250 Are the data objects raw data or Python objects that need to be
251 251 formatted before display? [default: False]
252 252 metadata : dict (optional)
253 253 Metadata to be associated with the specific mimetype output.
254 254 """
255 255 _display_mimetype('application/json', objs, **kwargs)
256 256
257 257
258 258 def display_javascript(*objs, **kwargs):
259 259 """Display the Javascript representation of an object.
260 260
261 261 Parameters
262 262 ----------
263 263 objs : tuple of objects
264 264 The Python objects to display, or if raw=True raw javascript data to
265 265 display.
266 266 raw : bool
267 267 Are the data objects raw data or Python objects that need to be
268 268 formatted before display? [default: False]
269 269 metadata : dict (optional)
270 270 Metadata to be associated with the specific mimetype output.
271 271 """
272 272 _display_mimetype('application/javascript', objs, **kwargs)
273 273
274
275 def display_pdf(*objs, **kwargs):
276 """Display the PDF representation of an object.
277
278 Parameters
279 ----------
280 objs : tuple of objects
281 The Python objects to display, or if raw=True raw javascript data to
282 display.
283 raw : bool
284 Are the data objects raw data or Python objects that need to be
285 formatted before display? [default: False]
286 metadata : dict (optional)
287 Metadata to be associated with the specific mimetype output.
288 """
289 _display_mimetype('application/pdf', objs, **kwargs)
290
291
274 292 #-----------------------------------------------------------------------------
275 293 # Smart classes
276 294 #-----------------------------------------------------------------------------
277 295
278 296
279 297 class DisplayObject(object):
280 298 """An object that wraps data to be displayed."""
281 299
282 300 _read_flags = 'r'
283 301
284 302 def __init__(self, data=None, url=None, filename=None):
285 303 """Create a display object given raw data.
286 304
287 305 When this object is returned by an expression or passed to the
288 306 display function, it will result in the data being displayed
289 307 in the frontend. The MIME type of the data should match the
290 308 subclasses used, so the Png subclass should be used for 'image/png'
291 309 data. If the data is a URL, the data will first be downloaded
292 310 and then displayed. If
293 311
294 312 Parameters
295 313 ----------
296 314 data : unicode, str or bytes
297 315 The raw data or a URL or file to load the data from
298 316 url : unicode
299 317 A URL to download the data from.
300 318 filename : unicode
301 319 Path to a local file to load the data from.
302 320 """
303 321 if data is not None and isinstance(data, string_types):
304 322 if data.startswith('http') and url is None:
305 323 url = data
306 324 filename = None
307 325 data = None
308 326 elif _safe_exists(data) and filename is None:
309 327 url = None
310 328 filename = data
311 329 data = None
312 330
313 331 self.data = data
314 332 self.url = url
315 333 self.filename = None if filename is None else unicode_type(filename)
316 334
317 335 self.reload()
318 336 self._check_data()
319 337
320 338 def _check_data(self):
321 339 """Override in subclasses if there's something to check."""
322 340 pass
323 341
324 342 def reload(self):
325 343 """Reload the raw data from file or URL."""
326 344 if self.filename is not None:
327 345 with open(self.filename, self._read_flags) as f:
328 346 self.data = f.read()
329 347 elif self.url is not None:
330 348 try:
331 349 try:
332 350 from urllib.request import urlopen # Py3
333 351 except ImportError:
334 352 from urllib2 import urlopen
335 353 response = urlopen(self.url)
336 354 self.data = response.read()
337 355 # extract encoding from header, if there is one:
338 356 encoding = None
339 357 for sub in response.headers['content-type'].split(';'):
340 358 sub = sub.strip()
341 359 if sub.startswith('charset'):
342 360 encoding = sub.split('=')[-1].strip()
343 361 break
344 362 # decode data, if an encoding was specified
345 363 if encoding:
346 364 self.data = self.data.decode(encoding, 'replace')
347 365 except:
348 366 self.data = None
349 367
350 368 class TextDisplayObject(DisplayObject):
351 369 """Validate that display data is text"""
352 370 def _check_data(self):
353 371 if self.data is not None and not isinstance(self.data, string_types):
354 372 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
355 373
356 374 class Pretty(TextDisplayObject):
357 375
358 376 def _repr_pretty_(self):
359 377 return self.data
360 378
361 379
362 380 class HTML(TextDisplayObject):
363 381
364 382 def _repr_html_(self):
365 383 return self.data
366 384
367 385 def __html__(self):
368 386 """
369 387 This method exists to inform other HTML-using modules (e.g. Markupsafe,
370 388 htmltag, etc) that this object is HTML and does not need things like
371 389 special characters (<>&) escaped.
372 390 """
373 391 return self._repr_html_()
374 392
375 393
376 394 class Math(TextDisplayObject):
377 395
378 396 def _repr_latex_(self):
379 397 s = self.data.strip('$')
380 398 return "$$%s$$" % s
381 399
382 400
383 401 class Latex(TextDisplayObject):
384 402
385 403 def _repr_latex_(self):
386 404 return self.data
387 405
388 406
389 407 class SVG(DisplayObject):
390 408
391 409 # wrap data in a property, which extracts the <svg> tag, discarding
392 410 # document headers
393 411 _data = None
394 412
395 413 @property
396 414 def data(self):
397 415 return self._data
398 416
399 417 @data.setter
400 418 def data(self, svg):
401 419 if svg is None:
402 420 self._data = None
403 421 return
404 422 # parse into dom object
405 423 from xml.dom import minidom
406 424 svg = cast_bytes_py2(svg)
407 425 x = minidom.parseString(svg)
408 426 # get svg tag (should be 1)
409 427 found_svg = x.getElementsByTagName('svg')
410 428 if found_svg:
411 429 svg = found_svg[0].toxml()
412 430 else:
413 431 # fallback on the input, trust the user
414 432 # but this is probably an error.
415 433 pass
416 434 svg = cast_unicode(svg)
417 435 self._data = svg
418 436
419 437 def _repr_svg_(self):
420 438 return self.data
421 439
422 440
423 441 class JSON(TextDisplayObject):
424 442
425 443 def _repr_json_(self):
426 444 return self.data
427 445
428 446 css_t = """$("head").append($("<link/>").attr({
429 447 rel: "stylesheet",
430 448 type: "text/css",
431 449 href: "%s"
432 450 }));
433 451 """
434 452
435 453 lib_t1 = """$.getScript("%s", function () {
436 454 """
437 455 lib_t2 = """});
438 456 """
439 457
440 458 class Javascript(TextDisplayObject):
441 459
442 460 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
443 461 """Create a Javascript display object given raw data.
444 462
445 463 When this object is returned by an expression or passed to the
446 464 display function, it will result in the data being displayed
447 465 in the frontend. If the data is a URL, the data will first be
448 466 downloaded and then displayed.
449 467
450 468 In the Notebook, the containing element will be available as `element`,
451 469 and jQuery will be available. The output area starts hidden, so if
452 470 the js appends content to `element` that should be visible, then
453 471 it must call `container.show()` to unhide the area.
454 472
455 473 Parameters
456 474 ----------
457 475 data : unicode, str or bytes
458 476 The Javascript source code or a URL to download it from.
459 477 url : unicode
460 478 A URL to download the data from.
461 479 filename : unicode
462 480 Path to a local file to load the data from.
463 481 lib : list or str
464 482 A sequence of Javascript library URLs to load asynchronously before
465 483 running the source code. The full URLs of the libraries should
466 484 be given. A single Javascript library URL can also be given as a
467 485 string.
468 486 css: : list or str
469 487 A sequence of css files to load before running the source code.
470 488 The full URLs of the css files should be given. A single css URL
471 489 can also be given as a string.
472 490 """
473 491 if isinstance(lib, string_types):
474 492 lib = [lib]
475 493 elif lib is None:
476 494 lib = []
477 495 if isinstance(css, string_types):
478 496 css = [css]
479 497 elif css is None:
480 498 css = []
481 499 if not isinstance(lib, (list,tuple)):
482 500 raise TypeError('expected sequence, got: %r' % lib)
483 501 if not isinstance(css, (list,tuple)):
484 502 raise TypeError('expected sequence, got: %r' % css)
485 503 self.lib = lib
486 504 self.css = css
487 505 super(Javascript, self).__init__(data=data, url=url, filename=filename)
488 506
489 507 def _repr_javascript_(self):
490 508 r = ''
491 509 for c in self.css:
492 510 r += css_t % c
493 511 for l in self.lib:
494 512 r += lib_t1 % l
495 513 r += self.data
496 514 r += lib_t2*len(self.lib)
497 515 return r
498 516
499 517 # constants for identifying png/jpeg data
500 518 _PNG = b'\x89PNG\r\n\x1a\n'
501 519 _JPEG = b'\xff\xd8'
502 520
503 521 def _pngxy(data):
504 522 """read the (width, height) from a PNG header"""
505 523 ihdr = data.index(b'IHDR')
506 524 # next 8 bytes are width/height
507 525 w4h4 = data[ihdr+4:ihdr+12]
508 526 return struct.unpack('>ii', w4h4)
509 527
510 528 def _jpegxy(data):
511 529 """read the (width, height) from a JPEG header"""
512 530 # adapted from http://www.64lines.com/jpeg-width-height
513 531
514 532 idx = 4
515 533 while True:
516 534 block_size = struct.unpack('>H', data[idx:idx+2])[0]
517 535 idx = idx + block_size
518 536 if data[idx:idx+2] == b'\xFF\xC0':
519 537 # found Start of Frame
520 538 iSOF = idx
521 539 break
522 540 else:
523 541 # read another block
524 542 idx += 2
525 543
526 544 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
527 545 return w, h
528 546
529 547 class Image(DisplayObject):
530 548
531 549 _read_flags = 'rb'
532 550 _FMT_JPEG = u'jpeg'
533 551 _FMT_PNG = u'png'
534 552 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
535 553
536 554 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
537 555 """Create a PNG/JPEG image object given raw data.
538 556
539 557 When this object is returned by an input cell or passed to the
540 558 display function, it will result in the image being displayed
541 559 in the frontend.
542 560
543 561 Parameters
544 562 ----------
545 563 data : unicode, str or bytes
546 564 The raw image data or a URL or filename to load the data from.
547 565 This always results in embedded image data.
548 566 url : unicode
549 567 A URL to download the data from. If you specify `url=`,
550 568 the image data will not be embedded unless you also specify `embed=True`.
551 569 filename : unicode
552 570 Path to a local file to load the data from.
553 571 Images from a file are always embedded.
554 572 format : unicode
555 573 The format of the image data (png/jpeg/jpg). If a filename or URL is given
556 574 for format will be inferred from the filename extension.
557 575 embed : bool
558 576 Should the image data be embedded using a data URI (True) or be
559 577 loaded using an <img> tag. Set this to True if you want the image
560 578 to be viewable later with no internet connection in the notebook.
561 579
562 580 Default is `True`, unless the keyword argument `url` is set, then
563 581 default value is `False`.
564 582
565 583 Note that QtConsole is not able to display images if `embed` is set to `False`
566 584 width : int
567 585 Width to which to constrain the image in html
568 586 height : int
569 587 Height to which to constrain the image in html
570 588 retina : bool
571 589 Automatically set the width and height to half of the measured
572 590 width and height.
573 591 This only works for embedded images because it reads the width/height
574 592 from image data.
575 593 For non-embedded images, you can just set the desired display width
576 594 and height directly.
577 595
578 596 Examples
579 597 --------
580 598 # embedded image data, works in qtconsole and notebook
581 599 # when passed positionally, the first arg can be any of raw image data,
582 600 # a URL, or a filename from which to load image data.
583 601 # The result is always embedding image data for inline images.
584 602 Image('http://www.google.fr/images/srpr/logo3w.png')
585 603 Image('/path/to/image.jpg')
586 604 Image(b'RAW_PNG_DATA...')
587 605
588 606 # Specifying Image(url=...) does not embed the image data,
589 607 # it only generates `<img>` tag with a link to the source.
590 608 # This will not work in the qtconsole or offline.
591 609 Image(url='http://www.google.fr/images/srpr/logo3w.png')
592 610
593 611 """
594 612 if filename is not None:
595 613 ext = self._find_ext(filename)
596 614 elif url is not None:
597 615 ext = self._find_ext(url)
598 616 elif data is None:
599 617 raise ValueError("No image data found. Expecting filename, url, or data.")
600 618 elif isinstance(data, string_types) and (
601 619 data.startswith('http') or _safe_exists(data)
602 620 ):
603 621 ext = self._find_ext(data)
604 622 else:
605 623 ext = None
606 624
607 625 if ext is not None:
608 626 format = ext.lower()
609 627 if ext == u'jpg' or ext == u'jpeg':
610 628 format = self._FMT_JPEG
611 629 if ext == u'png':
612 630 format = self._FMT_PNG
613 631 elif isinstance(data, bytes) and format == 'png':
614 632 # infer image type from image data header,
615 633 # only if format might not have been specified.
616 634 if data[:2] == _JPEG:
617 635 format = 'jpeg'
618 636
619 637 self.format = unicode_type(format).lower()
620 638 self.embed = embed if embed is not None else (url is None)
621 639
622 640 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
623 641 raise ValueError("Cannot embed the '%s' image format" % (self.format))
624 642 self.width = width
625 643 self.height = height
626 644 self.retina = retina
627 645 super(Image, self).__init__(data=data, url=url, filename=filename)
628 646
629 647 if retina:
630 648 self._retina_shape()
631 649
632 650 def _retina_shape(self):
633 651 """load pixel-doubled width and height from image data"""
634 652 if not self.embed:
635 653 return
636 654 if self.format == 'png':
637 655 w, h = _pngxy(self.data)
638 656 elif self.format == 'jpeg':
639 657 w, h = _jpegxy(self.data)
640 658 else:
641 659 # retina only supports png
642 660 return
643 661 self.width = w // 2
644 662 self.height = h // 2
645 663
646 664 def reload(self):
647 665 """Reload the raw data from file or URL."""
648 666 if self.embed:
649 667 super(Image,self).reload()
650 668 if self.retina:
651 669 self._retina_shape()
652 670
653 671 def _repr_html_(self):
654 672 if not self.embed:
655 673 width = height = ''
656 674 if self.width:
657 675 width = ' width="%d"' % self.width
658 676 if self.height:
659 677 height = ' height="%d"' % self.height
660 678 return u'<img src="%s"%s%s/>' % (self.url, width, height)
661 679
662 680 def _data_and_metadata(self):
663 681 """shortcut for returning metadata with shape information, if defined"""
664 682 md = {}
665 683 if self.width:
666 684 md['width'] = self.width
667 685 if self.height:
668 686 md['height'] = self.height
669 687 if md:
670 688 return self.data, md
671 689 else:
672 690 return self.data
673 691
674 692 def _repr_png_(self):
675 693 if self.embed and self.format == u'png':
676 694 return self._data_and_metadata()
677 695
678 696 def _repr_jpeg_(self):
679 697 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
680 698 return self._data_and_metadata()
681 699
682 700 def _find_ext(self, s):
683 701 return unicode_type(s.split('.')[-1].lower())
684 702
685 703
686 704 def clear_output(wait=False):
687 705 """Clear the output of the current cell receiving output.
688 706
689 707 Parameters
690 708 ----------
691 709 wait : bool [default: false]
692 710 Wait to clear the output until new output is available to replace it."""
693 711 from IPython.core.interactiveshell import InteractiveShell
694 712 if InteractiveShell.initialized():
695 713 InteractiveShell.instance().display_pub.clear_output(wait)
696 714 else:
697 715 from IPython.utils import io
698 716 print('\033[2K\r', file=io.stdout, end='')
699 717 io.stdout.flush()
700 718 print('\033[2K\r', file=io.stderr, end='')
701 719 io.stderr.flush()
@@ -1,825 +1,846 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Display formatters.
3 3
4 4 Inheritance diagram:
5 5
6 6 .. inheritance-diagram:: IPython.core.formatters
7 7 :parts: 3
8 8
9 9 Authors:
10 10
11 11 * Robert Kern
12 12 * Brian Granger
13 13 """
14 14 #-----------------------------------------------------------------------------
15 15 # Copyright (C) 2010-2011, IPython Development Team.
16 16 #
17 17 # Distributed under the terms of the Modified BSD License.
18 18 #
19 19 # The full license is in the file COPYING.txt, distributed with this software.
20 20 #-----------------------------------------------------------------------------
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Imports
24 24 #-----------------------------------------------------------------------------
25 25
26 26 # Stdlib imports
27 27 import abc
28 28 import sys
29 29 import warnings
30 30
31 31 from IPython.external.decorator import decorator
32 32
33 33 # Our own imports
34 34 from IPython.config.configurable import Configurable
35 35 from IPython.lib import pretty
36 36 from IPython.utils import io
37 37 from IPython.utils.traitlets import (
38 38 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
39 39 )
40 40 from IPython.utils.warn import warn
41 41 from IPython.utils.py3compat import (
42 42 unicode_to_str, with_metaclass, PY3, string_types, unicode_type,
43 43 )
44 44
45 45 if PY3:
46 46 from io import StringIO
47 47 else:
48 48 from StringIO import StringIO
49 49
50 50
51 51 #-----------------------------------------------------------------------------
52 52 # The main DisplayFormatter class
53 53 #-----------------------------------------------------------------------------
54 54
55 55 class DisplayFormatter(Configurable):
56 56
57 57 # When set to true only the default plain text formatter will be used.
58 58 plain_text_only = Bool(False, config=True)
59 59 def _plain_text_only_changed(self, name, old, new):
60 60 warnings.warn("""DisplayFormatter.plain_text_only is deprecated.
61 61
62 62 Use DisplayFormatter.active_types = ['text/plain']
63 63 for the same effect.
64 64 """, DeprecationWarning)
65 65 if new:
66 66 self.active_types = ['text/plain']
67 67 else:
68 68 self.active_types = self.format_types
69 69
70 70 active_types = List(Unicode, config=True,
71 71 help="""List of currently active mime-types to display.
72 72 You can use this to set a white-list for formats to display.
73 73
74 74 Most users will not need to change this value.
75 75 """)
76 76 def _active_types_default(self):
77 77 return self.format_types
78 78
79 79 def _active_types_changed(self, name, old, new):
80 80 for key, formatter in self.formatters.items():
81 81 if key in new:
82 82 formatter.enabled = True
83 83 else:
84 84 formatter.enabled = False
85 85
86 86 # A dict of formatter whose keys are format types (MIME types) and whose
87 87 # values are subclasses of BaseFormatter.
88 88 formatters = Dict()
89 89 def _formatters_default(self):
90 90 """Activate the default formatters."""
91 91 formatter_classes = [
92 92 PlainTextFormatter,
93 93 HTMLFormatter,
94 94 SVGFormatter,
95 95 PNGFormatter,
96 PDFFormatter,
96 97 JPEGFormatter,
97 98 LatexFormatter,
98 99 JSONFormatter,
99 100 JavascriptFormatter
100 101 ]
101 102 d = {}
102 103 for cls in formatter_classes:
103 104 f = cls(parent=self)
104 105 d[f.format_type] = f
105 106 return d
106 107
107 108 def format(self, obj, include=None, exclude=None):
108 109 """Return a format data dict for an object.
109 110
110 111 By default all format types will be computed.
111 112
112 113 The following MIME types are currently implemented:
113 114
114 115 * text/plain
115 116 * text/html
116 117 * text/latex
117 118 * application/json
118 119 * application/javascript
120 * application/pdf
119 121 * image/png
120 122 * image/jpeg
121 123 * image/svg+xml
122 124
123 125 Parameters
124 126 ----------
125 127 obj : object
126 128 The Python object whose format data will be computed.
127 129 include : list or tuple, optional
128 130 A list of format type strings (MIME types) to include in the
129 131 format data dict. If this is set *only* the format types included
130 132 in this list will be computed.
131 133 exclude : list or tuple, optional
132 134 A list of format type string (MIME types) to exclude in the format
133 135 data dict. If this is set all format types will be computed,
134 136 except for those included in this argument.
135 137
136 138 Returns
137 139 -------
138 140 (format_dict, metadata_dict) : tuple of two dicts
139 141
140 142 format_dict is a dictionary of key/value pairs, one of each format that was
141 143 generated for the object. The keys are the format types, which
142 144 will usually be MIME type strings and the values and JSON'able
143 145 data structure containing the raw data for the representation in
144 146 that format.
145 147
146 148 metadata_dict is a dictionary of metadata about each mime-type output.
147 149 Its keys will be a strict subset of the keys in format_dict.
148 150 """
149 151 format_dict = {}
150 152 md_dict = {}
151 153
152 154 for format_type, formatter in self.formatters.items():
153 155 if include and format_type not in include:
154 156 continue
155 157 if exclude and format_type in exclude:
156 158 continue
157 159
158 160 md = None
159 161 try:
160 162 data = formatter(obj)
161 163 except:
162 164 # FIXME: log the exception
163 165 raise
164 166
165 167 # formatters can return raw data or (data, metadata)
166 168 if isinstance(data, tuple) and len(data) == 2:
167 169 data, md = data
168 170
169 171 if data is not None:
170 172 format_dict[format_type] = data
171 173 if md is not None:
172 174 md_dict[format_type] = md
173 175
174 176 return format_dict, md_dict
175 177
176 178 @property
177 179 def format_types(self):
178 180 """Return the format types (MIME types) of the active formatters."""
179 181 return list(self.formatters.keys())
180 182
181 183
182 184 #-----------------------------------------------------------------------------
183 185 # Formatters for specific format types (text, html, svg, etc.)
184 186 #-----------------------------------------------------------------------------
185 187
186 188 class FormatterWarning(UserWarning):
187 189 """Warning class for errors in formatters"""
188 190
189 191 @decorator
190 192 def warn_format_error(method, self, *args, **kwargs):
191 193 """decorator for warning on failed format call"""
192 194 try:
193 195 r = method(self, *args, **kwargs)
194 196 except NotImplementedError as e:
195 197 # don't warn on NotImplementedErrors
196 198 return None
197 199 except Exception as e:
198 200 warnings.warn("Exception in %s formatter: %s" % (self.format_type, e),
199 201 FormatterWarning,
200 202 )
201 203 return None
202 204 if r is None or isinstance(r, self._return_type) or \
203 205 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
204 206 return r
205 207 else:
206 208 warnings.warn(
207 209 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
208 210 (self.format_type, type(r), self._return_type, pretty._safe_repr(args[0])),
209 211 FormatterWarning
210 212 )
211 213
212 214
213 215 class FormatterABC(with_metaclass(abc.ABCMeta, object)):
214 216 """ Abstract base class for Formatters.
215 217
216 218 A formatter is a callable class that is responsible for computing the
217 219 raw format data for a particular format type (MIME type). For example,
218 220 an HTML formatter would have a format type of `text/html` and would return
219 221 the HTML representation of the object when called.
220 222 """
221 223
222 224 # The format type of the data returned, usually a MIME type.
223 225 format_type = 'text/plain'
224 226
225 227 # Is the formatter enabled...
226 228 enabled = True
227 229
228 230 @abc.abstractmethod
229 231 @warn_format_error
230 232 def __call__(self, obj):
231 233 """Return a JSON'able representation of the object.
232 234
233 235 If the object cannot be formatted by this formatter,
234 236 warn and return None.
235 237 """
236 238 return repr(obj)
237 239
238 240
239 241 def _mod_name_key(typ):
240 242 """Return a (__module__, __name__) tuple for a type.
241 243
242 244 Used as key in Formatter.deferred_printers.
243 245 """
244 246 module = getattr(typ, '__module__', None)
245 247 name = getattr(typ, '__name__', None)
246 248 return (module, name)
247 249
248 250
249 251 def _get_type(obj):
250 252 """Return the type of an instance (old and new-style)"""
251 253 return getattr(obj, '__class__', None) or type(obj)
252 254
253 255 _raise_key_error = object()
254 256
255 257
256 258 class BaseFormatter(Configurable):
257 259 """A base formatter class that is configurable.
258 260
259 261 This formatter should usually be used as the base class of all formatters.
260 262 It is a traited :class:`Configurable` class and includes an extensible
261 263 API for users to determine how their objects are formatted. The following
262 264 logic is used to find a function to format an given object.
263 265
264 266 1. The object is introspected to see if it has a method with the name
265 267 :attr:`print_method`. If is does, that object is passed to that method
266 268 for formatting.
267 269 2. If no print method is found, three internal dictionaries are consulted
268 270 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
269 271 and :attr:`deferred_printers`.
270 272
271 273 Users should use these dictionaries to register functions that will be
272 274 used to compute the format data for their objects (if those objects don't
273 275 have the special print methods). The easiest way of using these
274 276 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
275 277 methods.
276 278
277 279 If no function/callable is found to compute the format data, ``None`` is
278 280 returned and this format type is not used.
279 281 """
280 282
281 283 format_type = Unicode('text/plain')
282 284 _return_type = string_types
283 285
284 286 enabled = Bool(True, config=True)
285 287
286 288 print_method = ObjectName('__repr__')
287 289
288 290 # The singleton printers.
289 291 # Maps the IDs of the builtin singleton objects to the format functions.
290 292 singleton_printers = Dict(config=True)
291 293
292 294 # The type-specific printers.
293 295 # Map type objects to the format functions.
294 296 type_printers = Dict(config=True)
295 297
296 298 # The deferred-import type-specific printers.
297 299 # Map (modulename, classname) pairs to the format functions.
298 300 deferred_printers = Dict(config=True)
299 301
300 302 @warn_format_error
301 303 def __call__(self, obj):
302 304 """Compute the format for an object."""
303 305 if self.enabled:
304 306 # lookup registered printer
305 307 try:
306 308 printer = self.lookup(obj)
307 309 except KeyError:
308 310 pass
309 311 else:
310 312 return printer(obj)
311 313 # Finally look for special method names
312 314 method = pretty._safe_getattr(obj, self.print_method, None)
313 315 if method is not None:
314 316 return method()
315 317 return None
316 318 else:
317 319 return None
318 320
319 321 def __contains__(self, typ):
320 322 """map in to lookup_by_type"""
321 323 try:
322 324 self.lookup_by_type(typ)
323 325 except KeyError:
324 326 return False
325 327 else:
326 328 return True
327 329
328 330 def lookup(self, obj):
329 331 """Look up the formatter for a given instance.
330 332
331 333 Parameters
332 334 ----------
333 335 obj : object instance
334 336
335 337 Returns
336 338 -------
337 339 f : callable
338 340 The registered formatting callable for the type.
339 341
340 342 Raises
341 343 ------
342 344 KeyError if the type has not been registered.
343 345 """
344 346 # look for singleton first
345 347 obj_id = id(obj)
346 348 if obj_id in self.singleton_printers:
347 349 return self.singleton_printers[obj_id]
348 350 # then lookup by type
349 351 return self.lookup_by_type(_get_type(obj))
350 352
351 353 def lookup_by_type(self, typ):
352 354 """Look up the registered formatter for a type.
353 355
354 356 Parameters
355 357 ----------
356 358 typ : type or '__module__.__name__' string for a type
357 359
358 360 Returns
359 361 -------
360 362 f : callable
361 363 The registered formatting callable for the type.
362 364
363 365 Raises
364 366 ------
365 367 KeyError if the type has not been registered.
366 368 """
367 369 if isinstance(typ, string_types):
368 370 typ_key = tuple(typ.rsplit('.',1))
369 371 if typ_key not in self.deferred_printers:
370 372 # We may have it cached in the type map. We will have to
371 373 # iterate over all of the types to check.
372 374 for cls in self.type_printers:
373 375 if _mod_name_key(cls) == typ_key:
374 376 return self.type_printers[cls]
375 377 else:
376 378 return self.deferred_printers[typ_key]
377 379 else:
378 380 for cls in pretty._get_mro(typ):
379 381 if cls in self.type_printers or self._in_deferred_types(cls):
380 382 return self.type_printers[cls]
381 383
382 384 # If we have reached here, the lookup failed.
383 385 raise KeyError("No registered printer for {0!r}".format(typ))
384 386
385 387 def for_type(self, typ, func=None):
386 388 """Add a format function for a given type.
387 389
388 390 Parameters
389 391 -----------
390 392 typ : type or '__module__.__name__' string for a type
391 393 The class of the object that will be formatted using `func`.
392 394 func : callable
393 395 A callable for computing the format data.
394 396 `func` will be called with the object to be formatted,
395 397 and will return the raw data in this formatter's format.
396 398 Subclasses may use a different call signature for the
397 399 `func` argument.
398 400
399 401 If `func` is None or not specified, there will be no change,
400 402 only returning the current value.
401 403
402 404 Returns
403 405 -------
404 406 oldfunc : callable
405 407 The currently registered callable.
406 408 If you are registering a new formatter,
407 409 this will be the previous value (to enable restoring later).
408 410 """
409 411 # if string given, interpret as 'pkg.module.class_name'
410 412 if isinstance(typ, string_types):
411 413 type_module, type_name = typ.rsplit('.', 1)
412 414 return self.for_type_by_name(type_module, type_name, func)
413 415
414 416 try:
415 417 oldfunc = self.lookup_by_type(typ)
416 418 except KeyError:
417 419 oldfunc = None
418 420
419 421 if func is not None:
420 422 self.type_printers[typ] = func
421 423
422 424 return oldfunc
423 425
424 426 def for_type_by_name(self, type_module, type_name, func=None):
425 427 """Add a format function for a type specified by the full dotted
426 428 module and name of the type, rather than the type of the object.
427 429
428 430 Parameters
429 431 ----------
430 432 type_module : str
431 433 The full dotted name of the module the type is defined in, like
432 434 ``numpy``.
433 435 type_name : str
434 436 The name of the type (the class name), like ``dtype``
435 437 func : callable
436 438 A callable for computing the format data.
437 439 `func` will be called with the object to be formatted,
438 440 and will return the raw data in this formatter's format.
439 441 Subclasses may use a different call signature for the
440 442 `func` argument.
441 443
442 444 If `func` is None or unspecified, there will be no change,
443 445 only returning the current value.
444 446
445 447 Returns
446 448 -------
447 449 oldfunc : callable
448 450 The currently registered callable.
449 451 If you are registering a new formatter,
450 452 this will be the previous value (to enable restoring later).
451 453 """
452 454 key = (type_module, type_name)
453 455
454 456 try:
455 457 oldfunc = self.lookup_by_type("%s.%s" % key)
456 458 except KeyError:
457 459 oldfunc = None
458 460
459 461 if func is not None:
460 462 self.deferred_printers[key] = func
461 463 return oldfunc
462 464
463 465 def pop(self, typ, default=_raise_key_error):
464 466 """Pop a formatter for the given type.
465 467
466 468 Parameters
467 469 ----------
468 470 typ : type or '__module__.__name__' string for a type
469 471 default : object
470 472 value to be returned if no formatter is registered for typ.
471 473
472 474 Returns
473 475 -------
474 476 obj : object
475 477 The last registered object for the type.
476 478
477 479 Raises
478 480 ------
479 481 KeyError if the type is not registered and default is not specified.
480 482 """
481 483
482 484 if isinstance(typ, string_types):
483 485 typ_key = tuple(typ.rsplit('.',1))
484 486 if typ_key not in self.deferred_printers:
485 487 # We may have it cached in the type map. We will have to
486 488 # iterate over all of the types to check.
487 489 for cls in self.type_printers:
488 490 if _mod_name_key(cls) == typ_key:
489 491 old = self.type_printers.pop(cls)
490 492 break
491 493 else:
492 494 old = default
493 495 else:
494 496 old = self.deferred_printers.pop(typ_key)
495 497 else:
496 498 if typ in self.type_printers:
497 499 old = self.type_printers.pop(typ)
498 500 else:
499 501 old = self.deferred_printers.pop(_mod_name_key(typ), default)
500 502 if old is _raise_key_error:
501 503 raise KeyError("No registered value for {0!r}".format(typ))
502 504 return old
503 505
504 506 def _in_deferred_types(self, cls):
505 507 """
506 508 Check if the given class is specified in the deferred type registry.
507 509
508 510 Successful matches will be moved to the regular type registry for future use.
509 511 """
510 512 mod = getattr(cls, '__module__', None)
511 513 name = getattr(cls, '__name__', None)
512 514 key = (mod, name)
513 515 if key in self.deferred_printers:
514 516 # Move the printer over to the regular registry.
515 517 printer = self.deferred_printers.pop(key)
516 518 self.type_printers[cls] = printer
517 519 return True
518 520 return False
519 521
520 522
521 523 class PlainTextFormatter(BaseFormatter):
522 524 """The default pretty-printer.
523 525
524 526 This uses :mod:`IPython.lib.pretty` to compute the format data of
525 527 the object. If the object cannot be pretty printed, :func:`repr` is used.
526 528 See the documentation of :mod:`IPython.lib.pretty` for details on
527 529 how to write pretty printers. Here is a simple example::
528 530
529 531 def dtype_pprinter(obj, p, cycle):
530 532 if cycle:
531 533 return p.text('dtype(...)')
532 534 if hasattr(obj, 'fields'):
533 535 if obj.fields is None:
534 536 p.text(repr(obj))
535 537 else:
536 538 p.begin_group(7, 'dtype([')
537 539 for i, field in enumerate(obj.descr):
538 540 if i > 0:
539 541 p.text(',')
540 542 p.breakable()
541 543 p.pretty(field)
542 544 p.end_group(7, '])')
543 545 """
544 546
545 547 # The format type of data returned.
546 548 format_type = Unicode('text/plain')
547 549
548 550 # This subclass ignores this attribute as it always need to return
549 551 # something.
550 552 enabled = Bool(True, config=False)
551 553
552 554 # Look for a _repr_pretty_ methods to use for pretty printing.
553 555 print_method = ObjectName('_repr_pretty_')
554 556
555 557 # Whether to pretty-print or not.
556 558 pprint = Bool(True, config=True)
557 559
558 560 # Whether to be verbose or not.
559 561 verbose = Bool(False, config=True)
560 562
561 563 # The maximum width.
562 564 max_width = Integer(79, config=True)
563 565
564 566 # The newline character.
565 567 newline = Unicode('\n', config=True)
566 568
567 569 # format-string for pprinting floats
568 570 float_format = Unicode('%r')
569 571 # setter for float precision, either int or direct format-string
570 572 float_precision = CUnicode('', config=True)
571 573
572 574 def _float_precision_changed(self, name, old, new):
573 575 """float_precision changed, set float_format accordingly.
574 576
575 577 float_precision can be set by int or str.
576 578 This will set float_format, after interpreting input.
577 579 If numpy has been imported, numpy print precision will also be set.
578 580
579 581 integer `n` sets format to '%.nf', otherwise, format set directly.
580 582
581 583 An empty string returns to defaults (repr for float, 8 for numpy).
582 584
583 585 This parameter can be set via the '%precision' magic.
584 586 """
585 587
586 588 if '%' in new:
587 589 # got explicit format string
588 590 fmt = new
589 591 try:
590 592 fmt%3.14159
591 593 except Exception:
592 594 raise ValueError("Precision must be int or format string, not %r"%new)
593 595 elif new:
594 596 # otherwise, should be an int
595 597 try:
596 598 i = int(new)
597 599 assert i >= 0
598 600 except ValueError:
599 601 raise ValueError("Precision must be int or format string, not %r"%new)
600 602 except AssertionError:
601 603 raise ValueError("int precision must be non-negative, not %r"%i)
602 604
603 605 fmt = '%%.%if'%i
604 606 if 'numpy' in sys.modules:
605 607 # set numpy precision if it has been imported
606 608 import numpy
607 609 numpy.set_printoptions(precision=i)
608 610 else:
609 611 # default back to repr
610 612 fmt = '%r'
611 613 if 'numpy' in sys.modules:
612 614 import numpy
613 615 # numpy default is 8
614 616 numpy.set_printoptions(precision=8)
615 617 self.float_format = fmt
616 618
617 619 # Use the default pretty printers from IPython.lib.pretty.
618 620 def _singleton_printers_default(self):
619 621 return pretty._singleton_pprinters.copy()
620 622
621 623 def _type_printers_default(self):
622 624 d = pretty._type_pprinters.copy()
623 625 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
624 626 return d
625 627
626 628 def _deferred_printers_default(self):
627 629 return pretty._deferred_type_pprinters.copy()
628 630
629 631 #### FormatterABC interface ####
630 632
631 633 @warn_format_error
632 634 def __call__(self, obj):
633 635 """Compute the pretty representation of the object."""
634 636 if not self.pprint:
635 637 return pretty._safe_repr(obj)
636 638 else:
637 639 # This uses use StringIO, as cStringIO doesn't handle unicode.
638 640 stream = StringIO()
639 641 # self.newline.encode() is a quick fix for issue gh-597. We need to
640 642 # ensure that stream does not get a mix of unicode and bytestrings,
641 643 # or it will cause trouble.
642 644 printer = pretty.RepresentationPrinter(stream, self.verbose,
643 645 self.max_width, unicode_to_str(self.newline),
644 646 singleton_pprinters=self.singleton_printers,
645 647 type_pprinters=self.type_printers,
646 648 deferred_pprinters=self.deferred_printers)
647 649 printer.pretty(obj)
648 650 printer.flush()
649 651 return stream.getvalue()
650 652
651 653
652 654 class HTMLFormatter(BaseFormatter):
653 655 """An HTML formatter.
654 656
655 657 To define the callables that compute the HTML representation of your
656 658 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
657 659 or :meth:`for_type_by_name` methods to register functions that handle
658 660 this.
659 661
660 662 The return value of this formatter should be a valid HTML snippet that
661 663 could be injected into an existing DOM. It should *not* include the
662 664 ```<html>`` or ```<body>`` tags.
663 665 """
664 666 format_type = Unicode('text/html')
665 667
666 668 print_method = ObjectName('_repr_html_')
667 669
668 670
669 671 class SVGFormatter(BaseFormatter):
670 672 """An SVG formatter.
671 673
672 674 To define the callables that compute the SVG representation of your
673 675 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
674 676 or :meth:`for_type_by_name` methods to register functions that handle
675 677 this.
676 678
677 679 The return value of this formatter should be valid SVG enclosed in
678 680 ```<svg>``` tags, that could be injected into an existing DOM. It should
679 681 *not* include the ```<html>`` or ```<body>`` tags.
680 682 """
681 683 format_type = Unicode('image/svg+xml')
682 684
683 685 print_method = ObjectName('_repr_svg_')
684 686
685 687
686 688 class PNGFormatter(BaseFormatter):
687 689 """A PNG formatter.
688 690
689 691 To define the callables that compute the PNG representation of your
690 692 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
691 693 or :meth:`for_type_by_name` methods to register functions that handle
692 694 this.
693 695
694 696 The return value of this formatter should be raw PNG data, *not*
695 697 base64 encoded.
696 698 """
697 699 format_type = Unicode('image/png')
698 700
699 701 print_method = ObjectName('_repr_png_')
700 702
701 703 _return_type = (bytes, unicode_type)
702 704
703 705
704 706 class JPEGFormatter(BaseFormatter):
705 707 """A JPEG formatter.
706 708
707 709 To define the callables that compute the JPEG representation of your
708 710 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
709 711 or :meth:`for_type_by_name` methods to register functions that handle
710 712 this.
711 713
712 714 The return value of this formatter should be raw JPEG data, *not*
713 715 base64 encoded.
714 716 """
715 717 format_type = Unicode('image/jpeg')
716 718
717 719 print_method = ObjectName('_repr_jpeg_')
718 720
719 721 _return_type = (bytes, unicode_type)
720 722
721 723
722 724 class LatexFormatter(BaseFormatter):
723 725 """A LaTeX formatter.
724 726
725 727 To define the callables that compute the LaTeX representation of your
726 728 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
727 729 or :meth:`for_type_by_name` methods to register functions that handle
728 730 this.
729 731
730 732 The return value of this formatter should be a valid LaTeX equation,
731 733 enclosed in either ```$```, ```$$``` or another LaTeX equation
732 734 environment.
733 735 """
734 736 format_type = Unicode('text/latex')
735 737
736 738 print_method = ObjectName('_repr_latex_')
737 739
738 740
739 741 class JSONFormatter(BaseFormatter):
740 742 """A JSON string formatter.
741 743
742 744 To define the callables that compute the JSON string representation of
743 745 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
744 746 or :meth:`for_type_by_name` methods to register functions that handle
745 747 this.
746 748
747 749 The return value of this formatter should be a valid JSON string.
748 750 """
749 751 format_type = Unicode('application/json')
750 752
751 753 print_method = ObjectName('_repr_json_')
752 754
753 755
754 756 class JavascriptFormatter(BaseFormatter):
755 757 """A Javascript formatter.
756 758
757 759 To define the callables that compute the Javascript representation of
758 760 your objects, define a :meth:`_repr_javascript_` method or use the
759 761 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
760 762 that handle this.
761 763
762 764 The return value of this formatter should be valid Javascript code and
763 765 should *not* be enclosed in ```<script>``` tags.
764 766 """
765 767 format_type = Unicode('application/javascript')
766 768
767 769 print_method = ObjectName('_repr_javascript_')
768 770
771
772 class PDFFormatter(BaseFormatter):
773 """A PDF formatter.
774
775 To defined the callables that compute to PDF representation of your
776 objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
777 or :meth:`for_type_by_name` methods to register functions that handle
778 this.
779
780 The return value of this formatter should be raw PDF data, *not*
781 base64 encoded.
782 """
783 format_type = Unicode('application/pdf')
784
785 print_method = ObjectName('_repr_pdf_')
786
787
769 788 FormatterABC.register(BaseFormatter)
770 789 FormatterABC.register(PlainTextFormatter)
771 790 FormatterABC.register(HTMLFormatter)
772 791 FormatterABC.register(SVGFormatter)
773 792 FormatterABC.register(PNGFormatter)
793 FormatterABC.register(PDFFormatter)
774 794 FormatterABC.register(JPEGFormatter)
775 795 FormatterABC.register(LatexFormatter)
776 796 FormatterABC.register(JSONFormatter)
777 797 FormatterABC.register(JavascriptFormatter)
778 798
779 799
780 800 def format_display_data(obj, include=None, exclude=None):
781 801 """Return a format data dict for an object.
782 802
783 803 By default all format types will be computed.
784 804
785 805 The following MIME types are currently implemented:
786 806
787 807 * text/plain
788 808 * text/html
789 809 * text/latex
790 810 * application/json
791 811 * application/javascript
812 * application/pdf
792 813 * image/png
793 814 * image/jpeg
794 815 * image/svg+xml
795 816
796 817 Parameters
797 818 ----------
798 819 obj : object
799 820 The Python object whose format data will be computed.
800 821
801 822 Returns
802 823 -------
803 824 format_dict : dict
804 825 A dictionary of key/value pairs, one or each format that was
805 826 generated for the object. The keys are the format types, which
806 827 will usually be MIME type strings and the values and JSON'able
807 828 data structure containing the raw data for the representation in
808 829 that format.
809 830 include : list or tuple, optional
810 831 A list of format type strings (MIME types) to include in the
811 832 format data dict. If this is set *only* the format types included
812 833 in this list will be computed.
813 834 exclude : list or tuple, optional
814 835 A list of format type string (MIME types) to exclue in the format
815 836 data dict. If this is set all format types will be computed,
816 837 except for those included in this argument.
817 838 """
818 839 from IPython.core.interactiveshell import InteractiveShell
819 840
820 841 InteractiveShell.instance().display_formatter.format(
821 842 obj,
822 843 include,
823 844 exclude
824 845 )
825 846
@@ -1,282 +1,291 b''
1 1 """Tests for the Formatters."""
2 2
3 3 from math import pi
4 4
5 5 try:
6 6 import numpy
7 7 except:
8 8 numpy = None
9 9 import nose.tools as nt
10 10
11 from IPython.core.formatters import PlainTextFormatter, HTMLFormatter, _mod_name_key
11 from IPython.core.formatters import (
12 PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key
13 )
12 14 from IPython.utils.io import capture_output
13 15
14 16 class A(object):
15 17 def __repr__(self):
16 18 return 'A()'
17 19
18 20 class B(A):
19 21 def __repr__(self):
20 22 return 'B()'
21 23
22 24 class C:
23 25 pass
24 26
25 27 class BadPretty(object):
26 28 _repr_pretty_ = None
27 29
28 30 class GoodPretty(object):
29 31 def _repr_pretty_(self, pp, cycle):
30 32 pp.text('foo')
31 33
32 34 def __repr__(self):
33 35 return 'GoodPretty()'
34 36
35 37 def foo_printer(obj, pp, cycle):
36 38 pp.text('foo')
37 39
38 40 def test_pretty():
39 41 f = PlainTextFormatter()
40 42 f.for_type(A, foo_printer)
41 43 nt.assert_equal(f(A()), 'foo')
42 44 nt.assert_equal(f(B()), 'foo')
43 45 nt.assert_equal(f(GoodPretty()), 'foo')
44 46 # Just don't raise an exception for the following:
45 47 f(BadPretty())
46 48
47 49 f.pprint = False
48 50 nt.assert_equal(f(A()), 'A()')
49 51 nt.assert_equal(f(B()), 'B()')
50 52 nt.assert_equal(f(GoodPretty()), 'GoodPretty()')
51 53
52 54
53 55 def test_deferred():
54 56 f = PlainTextFormatter()
55 57
56 58 def test_precision():
57 59 """test various values for float_precision."""
58 60 f = PlainTextFormatter()
59 61 nt.assert_equal(f(pi), repr(pi))
60 62 f.float_precision = 0
61 63 if numpy:
62 64 po = numpy.get_printoptions()
63 65 nt.assert_equal(po['precision'], 0)
64 66 nt.assert_equal(f(pi), '3')
65 67 f.float_precision = 2
66 68 if numpy:
67 69 po = numpy.get_printoptions()
68 70 nt.assert_equal(po['precision'], 2)
69 71 nt.assert_equal(f(pi), '3.14')
70 72 f.float_precision = '%g'
71 73 if numpy:
72 74 po = numpy.get_printoptions()
73 75 nt.assert_equal(po['precision'], 2)
74 76 nt.assert_equal(f(pi), '3.14159')
75 77 f.float_precision = '%e'
76 78 nt.assert_equal(f(pi), '3.141593e+00')
77 79 f.float_precision = ''
78 80 if numpy:
79 81 po = numpy.get_printoptions()
80 82 nt.assert_equal(po['precision'], 8)
81 83 nt.assert_equal(f(pi), repr(pi))
82 84
83 85 def test_bad_precision():
84 86 """test various invalid values for float_precision."""
85 87 f = PlainTextFormatter()
86 88 def set_fp(p):
87 89 f.float_precision=p
88 90 nt.assert_raises(ValueError, set_fp, '%')
89 91 nt.assert_raises(ValueError, set_fp, '%.3f%i')
90 92 nt.assert_raises(ValueError, set_fp, 'foo')
91 93 nt.assert_raises(ValueError, set_fp, -1)
92 94
93 95 def test_for_type():
94 96 f = PlainTextFormatter()
95 97
96 98 # initial return, None
97 99 nt.assert_is(f.for_type(C, foo_printer), None)
98 100 # no func queries
99 101 nt.assert_is(f.for_type(C), foo_printer)
100 102 # shouldn't change anything
101 103 nt.assert_is(f.for_type(C), foo_printer)
102 104 # None should do the same
103 105 nt.assert_is(f.for_type(C, None), foo_printer)
104 106 nt.assert_is(f.for_type(C, None), foo_printer)
105 107
106 108 def test_for_type_string():
107 109 f = PlainTextFormatter()
108 110
109 111 mod = C.__module__
110 112
111 113 type_str = '%s.%s' % (C.__module__, 'C')
112 114
113 115 # initial return, None
114 116 nt.assert_is(f.for_type(type_str, foo_printer), None)
115 117 # no func queries
116 118 nt.assert_is(f.for_type(type_str), foo_printer)
117 119 nt.assert_in(_mod_name_key(C), f.deferred_printers)
118 120 nt.assert_is(f.for_type(C), foo_printer)
119 121 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
120 122 nt.assert_in(C, f.type_printers)
121 123
122 124 def test_for_type_by_name():
123 125 f = PlainTextFormatter()
124 126
125 127 mod = C.__module__
126 128
127 129 # initial return, None
128 130 nt.assert_is(f.for_type_by_name(mod, 'C', foo_printer), None)
129 131 # no func queries
130 132 nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
131 133 # shouldn't change anything
132 134 nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
133 135 # None should do the same
134 136 nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
135 137 nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
136 138
137 139 def test_lookup():
138 140 f = PlainTextFormatter()
139 141
140 142 f.for_type(C, foo_printer)
141 143 nt.assert_is(f.lookup(C()), foo_printer)
142 144 with nt.assert_raises(KeyError):
143 145 f.lookup(A())
144 146
145 147 def test_lookup_string():
146 148 f = PlainTextFormatter()
147 149 type_str = '%s.%s' % (C.__module__, 'C')
148 150
149 151 f.for_type(type_str, foo_printer)
150 152 nt.assert_is(f.lookup(C()), foo_printer)
151 153 # should move from deferred to imported dict
152 154 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
153 155 nt.assert_in(C, f.type_printers)
154 156
155 157 def test_lookup_by_type():
156 158 f = PlainTextFormatter()
157 159 f.for_type(C, foo_printer)
158 160 nt.assert_is(f.lookup_by_type(C), foo_printer)
159 161 type_str = '%s.%s' % (C.__module__, 'C')
160 162 with nt.assert_raises(KeyError):
161 163 f.lookup_by_type(A)
162 164
163 165 def test_lookup_by_type_string():
164 166 f = PlainTextFormatter()
165 167 type_str = '%s.%s' % (C.__module__, 'C')
166 168 f.for_type(type_str, foo_printer)
167 169
168 170 # verify insertion
169 171 nt.assert_in(_mod_name_key(C), f.deferred_printers)
170 172 nt.assert_not_in(C, f.type_printers)
171 173
172 174 nt.assert_is(f.lookup_by_type(type_str), foo_printer)
173 175 # lookup by string doesn't cause import
174 176 nt.assert_in(_mod_name_key(C), f.deferred_printers)
175 177 nt.assert_not_in(C, f.type_printers)
176 178
177 179 nt.assert_is(f.lookup_by_type(C), foo_printer)
178 180 # should move from deferred to imported dict
179 181 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
180 182 nt.assert_in(C, f.type_printers)
181 183
182 184 def test_in_formatter():
183 185 f = PlainTextFormatter()
184 186 f.for_type(C, foo_printer)
185 187 type_str = '%s.%s' % (C.__module__, 'C')
186 188 nt.assert_in(C, f)
187 189 nt.assert_in(type_str, f)
188 190
189 191 def test_string_in_formatter():
190 192 f = PlainTextFormatter()
191 193 type_str = '%s.%s' % (C.__module__, 'C')
192 194 f.for_type(type_str, foo_printer)
193 195 nt.assert_in(type_str, f)
194 196 nt.assert_in(C, f)
195 197
196 198 def test_pop():
197 199 f = PlainTextFormatter()
198 200 f.for_type(C, foo_printer)
199 201 nt.assert_is(f.lookup_by_type(C), foo_printer)
200 202 nt.assert_is(f.pop(C, None), foo_printer)
201 203 f.for_type(C, foo_printer)
202 204 nt.assert_is(f.pop(C), foo_printer)
203 205 with nt.assert_raises(KeyError):
204 206 f.lookup_by_type(C)
205 207 with nt.assert_raises(KeyError):
206 208 f.pop(C)
207 209 with nt.assert_raises(KeyError):
208 210 f.pop(A)
209 211 nt.assert_is(f.pop(A, None), None)
210 212
211 213 def test_pop_string():
212 214 f = PlainTextFormatter()
213 215 type_str = '%s.%s' % (C.__module__, 'C')
214 216
215 217 with nt.assert_raises(KeyError):
216 218 f.pop(type_str)
217 219
218 220 f.for_type(type_str, foo_printer)
219 221 f.pop(type_str)
220 222 with nt.assert_raises(KeyError):
221 223 f.lookup_by_type(C)
222 224 with nt.assert_raises(KeyError):
223 225 f.pop(type_str)
224 226
225 227 f.for_type(C, foo_printer)
226 228 nt.assert_is(f.pop(type_str, None), foo_printer)
227 229 with nt.assert_raises(KeyError):
228 230 f.lookup_by_type(C)
229 231 with nt.assert_raises(KeyError):
230 232 f.pop(type_str)
231 233 nt.assert_is(f.pop(type_str, None), None)
232 234
233 235
234 236 def test_warn_error_method():
235 237 f = HTMLFormatter()
236 238 class BadHTML(object):
237 239 def _repr_html_(self):
238 240 return 1/0
239 241 bad = BadHTML()
240 242 with capture_output() as captured:
241 243 result = f(bad)
242 244 nt.assert_is(result, None)
243 245 nt.assert_in("FormatterWarning", captured.stderr)
244 246 nt.assert_in("text/html", captured.stderr)
245 247 nt.assert_in("zero", captured.stderr)
246 248
247 249 def test_nowarn_notimplemented():
248 250 f = HTMLFormatter()
249 251 class HTMLNotImplemented(object):
250 252 def _repr_html_(self):
251 253 raise NotImplementedError
252 254 return 1/0
253 255 h = HTMLNotImplemented()
254 256 with capture_output() as captured:
255 257 result = f(h)
256 258 nt.assert_is(result, None)
257 259 nt.assert_not_in("FormatterWarning", captured.stderr)
258 260
259 261 def test_warn_error_for_type():
260 262 f = HTMLFormatter()
261 263 f.for_type(int, lambda i: name_error)
262 264 with capture_output() as captured:
263 265 result = f(5)
264 266 nt.assert_is(result, None)
265 267 nt.assert_in("FormatterWarning", captured.stderr)
266 268 nt.assert_in("text/html", captured.stderr)
267 269 nt.assert_in("name_error", captured.stderr)
268 270
269 271 def test_warn_error_pretty_method():
270 272 f = PlainTextFormatter()
271 273 class BadPretty(object):
272 274 def _repr_pretty_(self):
273 275 return "hello"
274 276 bad = BadPretty()
275 277 with capture_output() as captured:
276 278 result = f(bad)
277 279 nt.assert_is(result, None)
278 280 nt.assert_in("FormatterWarning", captured.stderr)
279 281 nt.assert_in("text/plain", captured.stderr)
280 282 nt.assert_in("argument", captured.stderr)
281 283
284 class MakePDF(object):
285 def _repr_pdf_(self):
286 return 'PDF'
282 287
288 def test_pdf_formatter():
289 pdf = MakePDF()
290 f = PDFFormatter()
291 nt.assert_equal(f(pdf), 'PDF')
@@ -1,247 +1,256 b''
1 1 """Utilities to manipulate JSON objects.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2010-2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING.txt, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 # stdlib
14 14 import math
15 15 import re
16 16 import types
17 17 from datetime import datetime
18 18
19 19 try:
20 20 # base64.encodestring is deprecated in Python 3.x
21 21 from base64 import encodebytes
22 22 except ImportError:
23 23 # Python 2.x
24 24 from base64 import encodestring as encodebytes
25 25
26 26 from IPython.utils import py3compat
27 27 from IPython.utils.py3compat import string_types, unicode_type, iteritems
28 28 from IPython.utils.encoding import DEFAULT_ENCODING
29 29 next_attr_name = '__next__' if py3compat.PY3 else 'next'
30 30
31 31 #-----------------------------------------------------------------------------
32 32 # Globals and constants
33 33 #-----------------------------------------------------------------------------
34 34
35 35 # timestamp formats
36 36 ISO8601 = "%Y-%m-%dT%H:%M:%S.%f"
37 37 ISO8601_PAT=re.compile(r"^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,6})Z?([\+\-]\d{2}:?\d{2})?$")
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Classes and functions
41 41 #-----------------------------------------------------------------------------
42 42
43 43 def rekey(dikt):
44 44 """Rekey a dict that has been forced to use str keys where there should be
45 45 ints by json."""
46 46 for k in dikt:
47 47 if isinstance(k, string_types):
48 48 ik=fk=None
49 49 try:
50 50 ik = int(k)
51 51 except ValueError:
52 52 try:
53 53 fk = float(k)
54 54 except ValueError:
55 55 continue
56 56 if ik is not None:
57 57 nk = ik
58 58 else:
59 59 nk = fk
60 60 if nk in dikt:
61 61 raise KeyError("already have key %r"%nk)
62 62 dikt[nk] = dikt.pop(k)
63 63 return dikt
64 64
65 65 def parse_date(s):
66 66 """parse an ISO8601 date string
67 67
68 68 If it is None or not a valid ISO8601 timestamp,
69 69 it will be returned unmodified.
70 70 Otherwise, it will return a datetime object.
71 71 """
72 72 if s is None:
73 73 return s
74 74 m = ISO8601_PAT.match(s)
75 75 if m:
76 76 # FIXME: add actual timezone support
77 77 # this just drops the timezone info
78 78 notz = m.groups()[0]
79 79 return datetime.strptime(notz, ISO8601)
80 80 return s
81 81
82 82 def extract_dates(obj):
83 83 """extract ISO8601 dates from unpacked JSON"""
84 84 if isinstance(obj, dict):
85 85 new_obj = {} # don't clobber
86 86 for k,v in iteritems(obj):
87 87 new_obj[k] = extract_dates(v)
88 88 obj = new_obj
89 89 elif isinstance(obj, (list, tuple)):
90 90 obj = [ extract_dates(o) for o in obj ]
91 91 elif isinstance(obj, string_types):
92 92 obj = parse_date(obj)
93 93 return obj
94 94
95 95 def squash_dates(obj):
96 96 """squash datetime objects into ISO8601 strings"""
97 97 if isinstance(obj, dict):
98 98 obj = dict(obj) # don't clobber
99 99 for k,v in iteritems(obj):
100 100 obj[k] = squash_dates(v)
101 101 elif isinstance(obj, (list, tuple)):
102 102 obj = [ squash_dates(o) for o in obj ]
103 103 elif isinstance(obj, datetime):
104 104 obj = obj.isoformat()
105 105 return obj
106 106
107 107 def date_default(obj):
108 108 """default function for packing datetime objects in JSON."""
109 109 if isinstance(obj, datetime):
110 110 return obj.isoformat()
111 111 else:
112 112 raise TypeError("%r is not JSON serializable"%obj)
113 113
114 114
115 115 # constants for identifying png/jpeg data
116 116 PNG = b'\x89PNG\r\n\x1a\n'
117 117 # front of PNG base64-encoded
118 118 PNG64 = b'iVBORw0KG'
119 119 JPEG = b'\xff\xd8'
120 120 # front of JPEG base64-encoded
121 121 JPEG64 = b'/9'
122 # front of PDF base64-encoded
123 PDF64 = b'JVBER'
122 124
123 125 def encode_images(format_dict):
124 126 """b64-encodes images in a displaypub format dict
125 127
126 128 Perhaps this should be handled in json_clean itself?
127 129
128 130 Parameters
129 131 ----------
130 132
131 133 format_dict : dict
132 134 A dictionary of display data keyed by mime-type
133 135
134 136 Returns
135 137 -------
136 138
137 139 format_dict : dict
138 140 A copy of the same dictionary,
139 but binary image data ('image/png' or 'image/jpeg')
141 but binary image data ('image/png', 'image/jpeg' or 'application/pdf')
140 142 is base64-encoded.
141 143
142 144 """
143 145 encoded = format_dict.copy()
144 146
145 147 pngdata = format_dict.get('image/png')
146 148 if isinstance(pngdata, bytes):
147 149 # make sure we don't double-encode
148 150 if not pngdata.startswith(PNG64):
149 151 pngdata = encodebytes(pngdata)
150 152 encoded['image/png'] = pngdata.decode('ascii')
151 153
152 154 jpegdata = format_dict.get('image/jpeg')
153 155 if isinstance(jpegdata, bytes):
154 156 # make sure we don't double-encode
155 157 if not jpegdata.startswith(JPEG64):
156 158 jpegdata = encodebytes(jpegdata)
157 159 encoded['image/jpeg'] = jpegdata.decode('ascii')
158 160
161 pdfdata = format_dict.get('application/pdf')
162 if isinstance(pdfdata, bytes):
163 # make sure we don't double-encode
164 if not pdfdata.startswith(PDF64):
165 pdfdata = encodebytes(pdfdata)
166 encoded['application/pdf'] = pdfdata.decode('ascii')
167
159 168 return encoded
160 169
161 170
162 171 def json_clean(obj):
163 172 """Clean an object to ensure it's safe to encode in JSON.
164 173
165 174 Atomic, immutable objects are returned unmodified. Sets and tuples are
166 175 converted to lists, lists are copied and dicts are also copied.
167 176
168 177 Note: dicts whose keys could cause collisions upon encoding (such as a dict
169 178 with both the number 1 and the string '1' as keys) will cause a ValueError
170 179 to be raised.
171 180
172 181 Parameters
173 182 ----------
174 183 obj : any python object
175 184
176 185 Returns
177 186 -------
178 187 out : object
179 188
180 189 A version of the input which will not cause an encoding error when
181 190 encoded as JSON. Note that this function does not *encode* its inputs,
182 191 it simply sanitizes it so that there will be no encoding errors later.
183 192
184 193 Examples
185 194 --------
186 195 >>> json_clean(4)
187 196 4
188 197 >>> json_clean(list(range(10)))
189 198 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
190 199 >>> sorted(json_clean(dict(x=1, y=2)).items())
191 200 [('x', 1), ('y', 2)]
192 201 >>> sorted(json_clean(dict(x=1, y=2, z=[1,2,3])).items())
193 202 [('x', 1), ('y', 2), ('z', [1, 2, 3])]
194 203 >>> json_clean(True)
195 204 True
196 205 """
197 206 # types that are 'atomic' and ok in json as-is.
198 207 atomic_ok = (unicode_type, type(None))
199 208
200 209 # containers that we need to convert into lists
201 210 container_to_list = (tuple, set, types.GeneratorType)
202 211
203 212 if isinstance(obj, float):
204 213 # cast out-of-range floats to their reprs
205 214 if math.isnan(obj) or math.isinf(obj):
206 215 return repr(obj)
207 216 return float(obj)
208 217
209 218 if isinstance(obj, int):
210 219 # cast int to int, in case subclasses override __str__ (e.g. boost enum, #4598)
211 220 if isinstance(obj, bool):
212 221 # bools are ints, but we don't want to cast them to 0,1
213 222 return obj
214 223 return int(obj)
215 224
216 225 if isinstance(obj, atomic_ok):
217 226 return obj
218 227
219 228 if isinstance(obj, bytes):
220 229 return obj.decode(DEFAULT_ENCODING, 'replace')
221 230
222 231 if isinstance(obj, container_to_list) or (
223 232 hasattr(obj, '__iter__') and hasattr(obj, next_attr_name)):
224 233 obj = list(obj)
225 234
226 235 if isinstance(obj, list):
227 236 return [json_clean(x) for x in obj]
228 237
229 238 if isinstance(obj, dict):
230 239 # First, validate that the dict won't lose data in conversion due to
231 240 # key collisions after stringification. This can happen with keys like
232 241 # True and 'true' or 1 and '1', which collide in JSON.
233 242 nkeys = len(obj)
234 243 nkeys_collapsed = len(set(map(str, obj)))
235 244 if nkeys != nkeys_collapsed:
236 245 raise ValueError('dict can not be safely converted to JSON: '
237 246 'key collision would lead to dropped values')
238 247 # If all OK, proceed by making the new dict that will be json-safe
239 248 out = {}
240 249 for k,v in iteritems(obj):
241 250 out[str(k)] = json_clean(v)
242 251 return out
243 252
244 253 # If we get here, we don't know how to handle the object, so we just get
245 254 # its repr and return that. This will catch lambdas, open sockets, class
246 255 # objects, and any other complicated contraption that json can't encode
247 256 return repr(obj)
@@ -1,147 +1,149 b''
1 1 """Test suite for our JSON utilities.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2010-2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING.txt, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 # stdlib
14 14 import datetime
15 15 import json
16 16 from base64 import decodestring
17 17
18 18 # third party
19 19 import nose.tools as nt
20 20
21 21 # our own
22 22 from IPython.utils import jsonutil, tz
23 23 from ..jsonutil import json_clean, encode_images
24 24 from ..py3compat import unicode_to_str, str_to_bytes, iteritems
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Test functions
28 28 #-----------------------------------------------------------------------------
29 29 class Int(int):
30 30 def __str__(self):
31 31 return 'Int(%i)' % self
32 32
33 33 def test():
34 34 # list of input/expected output. Use None for the expected output if it
35 35 # can be the same as the input.
36 36 pairs = [(1, None), # start with scalars
37 37 (1.0, None),
38 38 ('a', None),
39 39 (True, None),
40 40 (False, None),
41 41 (None, None),
42 42 # complex numbers for now just go to strings, as otherwise they
43 43 # are unserializable
44 44 (1j, '1j'),
45 45 # Containers
46 46 ([1, 2], None),
47 47 ((1, 2), [1, 2]),
48 48 (set([1, 2]), [1, 2]),
49 49 (dict(x=1), None),
50 50 ({'x': 1, 'y':[1,2,3], '1':'int'}, None),
51 51 # More exotic objects
52 52 ((x for x in range(3)), [0, 1, 2]),
53 53 (iter([1, 2]), [1, 2]),
54 54 (Int(5), 5),
55 55 ]
56 56
57 57 for val, jval in pairs:
58 58 if jval is None:
59 59 jval = val
60 60 out = json_clean(val)
61 61 # validate our cleanup
62 62 nt.assert_equal(out, jval)
63 63 # and ensure that what we return, indeed encodes cleanly
64 64 json.loads(json.dumps(out))
65 65
66 66
67 67
68 68 def test_encode_images():
69 69 # invalid data, but the header and footer are from real files
70 70 pngdata = b'\x89PNG\r\n\x1a\nblahblahnotactuallyvalidIEND\xaeB`\x82'
71 71 jpegdata = b'\xff\xd8\xff\xe0\x00\x10JFIFblahblahjpeg(\xa0\x0f\xff\xd9'
72 pdfdata = b'%PDF-1.\ntrailer<</Root<</Pages<</Kids[<</MediaBox[0 0 3 3]>>]>>>>>>'
72 73
73 74 fmt = {
74 75 'image/png' : pngdata,
75 76 'image/jpeg' : jpegdata,
77 'application/pdf' : pdfdata
76 78 }
77 79 encoded = encode_images(fmt)
78 80 for key, value in iteritems(fmt):
79 81 # encoded has unicode, want bytes
80 82 decoded = decodestring(encoded[key].encode('ascii'))
81 83 nt.assert_equal(decoded, value)
82 84 encoded2 = encode_images(encoded)
83 85 nt.assert_equal(encoded, encoded2)
84 86
85 87 b64_str = {}
86 88 for key, encoded in iteritems(encoded):
87 89 b64_str[key] = unicode_to_str(encoded)
88 90 encoded3 = encode_images(b64_str)
89 91 nt.assert_equal(encoded3, b64_str)
90 92 for key, value in iteritems(fmt):
91 93 # encoded3 has str, want bytes
92 94 decoded = decodestring(str_to_bytes(encoded3[key]))
93 95 nt.assert_equal(decoded, value)
94 96
95 97 def test_lambda():
96 98 jc = json_clean(lambda : 1)
97 99 assert isinstance(jc, str)
98 100 assert '<lambda>' in jc
99 101 json.dumps(jc)
100 102
101 103 def test_extract_dates():
102 104 timestamps = [
103 105 '2013-07-03T16:34:52.249482',
104 106 '2013-07-03T16:34:52.249482Z',
105 107 '2013-07-03T16:34:52.249482Z-0800',
106 108 '2013-07-03T16:34:52.249482Z+0800',
107 109 '2013-07-03T16:34:52.249482Z+08:00',
108 110 '2013-07-03T16:34:52.249482Z-08:00',
109 111 '2013-07-03T16:34:52.249482-0800',
110 112 '2013-07-03T16:34:52.249482+0800',
111 113 '2013-07-03T16:34:52.249482+08:00',
112 114 '2013-07-03T16:34:52.249482-08:00',
113 115 ]
114 116 extracted = jsonutil.extract_dates(timestamps)
115 117 ref = extracted[0]
116 118 for dt in extracted:
117 119 nt.assert_true(isinstance(dt, datetime.datetime))
118 120 nt.assert_equal(dt, ref)
119 121
120 122 def test_parse_ms_precision():
121 123 base = '2013-07-03T16:34:52.'
122 124 digits = '1234567890'
123 125
124 126 for i in range(len(digits)):
125 127 ts = base + digits[:i]
126 128 parsed = jsonutil.parse_date(ts)
127 129 if i >= 1 and i <= 6:
128 130 assert isinstance(parsed, datetime.datetime)
129 131 else:
130 132 assert isinstance(parsed, str)
131 133
132 134 def test_date_default():
133 135 data = dict(today=datetime.datetime.now(), utcnow=tz.utcnow())
134 136 jsondata = json.dumps(data, default=jsonutil.date_default)
135 137 nt.assert_in("+00", jsondata)
136 138 nt.assert_equal(jsondata.count("+00"), 1)
137 139 extracted = jsonutil.extract_dates(json.loads(jsondata))
138 140 for dt in extracted.values():
139 141 nt.assert_true(isinstance(dt, datetime.datetime))
140 142
141 143 def test_exception():
142 144 bad_dicts = [{1:'number', '1':'string'},
143 145 {True:'bool', 'True':'string'},
144 146 ]
145 147 for d in bad_dicts:
146 148 nt.assert_raises(ValueError, json_clean, d)
147 149
General Comments 0
You need to be logged in to leave comments. Login now